Maven + SpringMVC + Mybatis

Maven

打包 mvn clean package 部署 mvn clean install/deploy

在 properties 标签中指定版本 然后用deoendencyManagement 标签引用版本用来父工程统一版本

MyBatis

快速上手

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录

准备数据模型

CREATE DATABASE `mybatis-example`;

USE `mybatis-example`;

CREATE TABLE `t_emp`(
emp_id INT AUTO_INCREMENT,
emp_name CHAR(100),
emp_salary DOUBLE(10,5),
PRIMARY KEY(emp_id)
);

INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);

依赖导入

<dependencies>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>

<!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>

<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>

准备实体类 添加get set 方法

创建Mapper接口和MapperXML接口

/**
* Mapper接口
* */
public interface EmployeeMapper {
Employee selectEmployee(Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于mapper接口类的全路径 -->
<mapper namespace="com.jason.mapper.EmployeeMapper">

<!-- crud 操作
id = 方法名,resultType = 返回值类型,标签内编写SQL语句
mapper接口不能重载
-->
<select id="selectEmployee" resultType="com.jason.enity.Employee">
<!-- #{empId}代表动态传入的参数,并且进行赋值!后面详细讲解 -->
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_id = #{id}
</select>
</mapper>

准备MyBatis配置文件 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境 -->
<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 建立数据库连接的具体信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>

<mappers>
<!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
<!-- mapper标签:配置一个具体的Mapper映射文件 -->
<!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
<!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
<mapper resource="EmployeeMapper.xml"/>
</mappers>

</configuration>

测试类 结合springioc容器后 我们可以将获取Mapper对象之前的步骤全部省略

/**
* 测试类
* */
public class MyBatisTest {
@Test
public void testSelectEmployee() throws Exception {
// 1.创建SqlSessionFactory对象
// ①声明Mybatis全局配置文件的路径
String mybatisConfigFilePath = "mybatis-config.xml";
// ②以输入流的形式加载Mybatis配置文件
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
// ③基于读取Mybatis配置文件的输入流创建SqlSessionFactory对象,是“生产”SqlSession的“工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.使用SqlSessionFactory对象开启一个会话,表示java和数据库之间的会话 可以加入true实现自动提交
// 可省略session.commit()
SqlSession session = sessionFactory.openSession();
// 3.根据EmployeeMapper接口的Class对象获取Mapper接口类型的对象(动态代理技术)
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
// 4. 调用代理类方法既可以触发对应的SQL语句
Employee employee = employeeMapper.selectEmployee(1);
System.out.println("employee = " + employee);
// 4.关闭SqlSession
session.commit(); //提交事务 [查询擦操作不需要,其他需要]
session.close(); //关闭会话

}
}

添加日志输出

在 mybatis-config.xml中添加

<settings>
<!--开启mybatis的日志输出,选择system进行控制台输出-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

数据输入

#{key} 和 ${key} 的区别

 <!--
#{key}:占位符 + 赋值1. emp_id =? 2. ? = 赋值
${key}:字符串拼接 "emp_id =" +id
推荐使用 #{key} 防止注入攻击
但是?只能代替值的位置,不能代替容器名(表名,列名,sql关键字) 不能写 ?=?
总结: 动态值 使用#{key} 动态的列名 容器名 关键字 使用${key}
-->

类型传入时,按类型属性名传入

<insert id="insertEmployee">
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
</insert>

零散的简单型数据,可以在接口中添加注解来进行数据输入,也可以通过mybatis的默认机制,arg0,arg1… 或者 param1,param2…..

int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);
<update id="updateEmployee">
update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}
</update>

Map类型传入

int updateEmployeeByMap(Map<String, Object> paramMap);
<!--
传入map 如何指定key的值
key =map的key
-->
<insert>
insert into t_emp (emp_name,emp_salary) values(#{name},#{salary})
</insert>

数据输出

单个简单类型

<!--通过resultType指定查询返回值类型-->
<select id="selectEmpCount" resultType="int">
select count(*) from t_emp3
</select>

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases> <package name="domain.blog"/> </typeAliases>

返回实体对象类型

<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
<!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}
</select>

自动给字段设别名的方式过于繁琐,我们可以增加全局配置来自动识别对应关系,这样就可以用* 代替,或者直接写对象中的属性名

<!-- 在全局范围内对Mybatis进行配置 -->
<settings>
<!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
<!-- 规则要求数据库表字段命名方式:单词_单词 emp_id === empId -->
<!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>

</settings>

返回map类型 适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。

<select id="selectEmpNameAndMaxSalary" resultType="map">
<!--....-->
</select>

返回List类型 查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。

Mapper上的抽象方法

List<Employee> selectAll();
<select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">
<!--....-->
</select>

测试类中遍历List

自增主键回显 插入数据后 将自增主键的值设置到对象中

Mapper接口中的抽象方法

int insertEmployee(Employee employee);

sql

<!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
insert into t_emp(emp_name,emp_salary)
values(#{empName},#{empSalary})
</insert>

测试类

@Test
public void testSaveEmp() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setEmpName("john");
employee.setEmpSalary(666.66);
employeeMapper.insertEmployee(employee);
log.info("employee.getEmpId() = " + employee.getEmpId());
}

非自增长类型主键

而对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用

1.自己维护主键

@Test
public void testSaveEmp() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
String eId = UUID.randomUUID().toString().replace("-", "");
employee.setid(eId);
employeeMapper.insertEmployee(employee);
log.info("employee.getEmpId() = " + employee.getEmpId());
}

2.在查询之前 生成主键值

 <insert id="insertUser" parameterType="User">
<!--keyProperty查询结果给哪个属性赋值
resultType返回值的类型
order="before|after" sql语句是在插入语句之前还是之后执行
-->
<selectKey keyProperty="id" resultType="java.lang.String"
order="BEFORE">
SELECT REPLACE(UUID(),'-','');
<!--或者-->
SELECT UUID() as id
</selectKey>
INSERT INTO user (id, username, password)
VALUES (
#{id},
#{username},
#{password}
)
</insert>

实体类属性和数据库字段对应关系

  1. 别名对应,将字段的别名设置成和实体类属性一致。
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
<!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}
</select>
  1. 全局配置自动识别驼峰式命名规则
<!-- 使用settings对Mybatis全局进行设置 -->
<settings>
<!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  1. 使用resultMap(多用于多表查询)
<!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
<resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee">

<!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
<!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
<id column="emp_id" property="empId"/>

<!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
<result column="emp_name" property="empName"/>

<result column="emp_salary" property="empSalary"/>

</resultMap>

<!-- Employee selectEmployeeByRM(Integer empId); -->
<select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">

select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}

</select>

多表映射

设计方案

如果是一对一的 就在类中包含单个对方对象类型的属性。如果是一对多的,就在类中包含对方的集合对象属性

案例模拟

数据库

CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );

CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) );

INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');

INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1');

添加用户和订单的实体类

@Data
public class Customer {
private Integer customerId;
private String customerName;
private List<Order> orderList;// 体现的是对多的关系
}
@Data
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系
}

对一映射 (根据ID查询订单以及相关的用户信息)

订单对应用户是对一关系

OrderMapper接口

public interface OrderMapper {
Order selectOrderWithCustomer(Integer orderId);
}

OderMapper.xml配置文件(记得在全局中注册Mapper文件)

<!-- 创建resultMap实现“对一”关联关系映射 -->
<!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” -->
<!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 -->
<resultMap id="selectOrderWithCustomerResultMap" type="order">
<!-- 先设置Order自身属性和字段的对应关系 -->
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<!-- 使用association标签配置“对一”关联关系 -->
<!-- property属性:在Order类中对一的一端进行引用时使用的属性名 -->
<!-- javaType属性:一的一端类的全类名,记得在配置文件中添加别名 -->
<association property="customer" javaType="customer">
<!-- 配置Customer类的属性和字段名之间的对应关系 -->
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
</association>
</resultMap>

<!-- Order selectOrderWithCustomer(Integer orderId); -->
<select id="selectOrderWithCustomer" resultMap="selectOrderWithCustomerResultMap">

SELECT order_id,order_name,c.customer_id,customer_name
FROM t_order o
LEFT JOIN t_customer c
ON o.customer_id=c.customer_id
WHERE o.order_id=#{orderId}

</select>

测试类

@Slf4j
public class MyBatisTest {

private SqlSession session;
// junit会在每一个@Test方法前执行@BeforeEach方法
@BeforeEach
public void init() throws IOException {
session = new SqlSessionFactoryBuilder()
.build(
Resources.getResourceAsStream("mybatis-config.xml"))
.openSession();
}
@Test
public void testRelationshipToOne() {

OrderMapper orderMapper = session.getMapper(OrderMapper.class);
// 查询Order对象,检查是否同时查询了关联的Customer对象
Order order = orderMapper.selectOrderWithCustomer(2);
log.info("order = " + order);

}

// junit会在每一个@Test方法后执行@@AfterEach方法
@AfterEach
public void clear() {
session.commit();
session.close();
}
}

对多映射(查询客户和客户关联的订单信息)

CustomerMapper接口

public interface CustomerMapper {
Customer selectCustomerWithOrderList(Integer customerId);
}

CustomerMapper.xml文件 (记得在全局中注册Mapper文件)

<!-- 配置resultMap实现从Customer到OrderList的“对多”关联关系 -->
<resultMap id="selectCustomerWithOrderListResultMap" type="customer">
<!-- 映射Customer本身的属性 -->
<id column="customer_id" property="customerId"/>

<result column="customer_name" property="customerName"/>

<!-- collection标签:映射“对多”的关联关系 -->
<!-- property属性:在Customer类中,关联“多”的一端的属性名 -->
<!-- ofType属性:集合属性中元素的类型 -->
<collection property="orderList" ofType="order">
<!-- 映射Order的属性 -->
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
</collection>

</resultMap>

<!-- Customer selectCustomerWithOrderList(Integer customerId); -->
<select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">
SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
FROM t_customer c
LEFT JOIN t_order o
ON c.customer_id=o.customer_id
WHERE c.customer_id=#{customerId}
</select>

测试类

@Test
public void testRelationshipToMulti() {

CustomerMapper customerMapper = session.getMapper(CustomerMapper.class);
// 查询Customer对象同时将关联的Order集合查询出来
Customer customer = customerMapper.selectCustomerWithOrderList(1);
log.info("customer.getCustomerId() = " + customer.getCustomerId());
log.info("customer.getCustomerName() = " + customer.getCustomerName());
List<Order> orderList = customer.getOrderList();
for (Order order : orderList) {
log.info("order = " + order);
}
}

多表映射优化

我们可以将autoMappingBehavior设置为full,进行多表resultMap映射的时候,可以省略符合列和属性命名映射规则(列名=属性名,或者开启驼峰映射也可以自定映射)的result标签!

修改mybatis-config.xml

<!--开启resultMap自动映射 -->
<setting name="autoMappingBehavior" value="FULL"/>

修改CustomerMapper.xml

<resultMap id="selectCustomerWithOrderListResultMap"  type="customer">
<!--就可以只保留他的ID-->
<id column="customer_id" property="customerId"/>
<collection property="orderList" ofType="order">
<id column="order_id" property="orderId"/>
</collection>
</resultMap>

总结

关联关系 配置项关键词 所在配置文件和具体位置
对一 association标签/javaType属性/property属性 Mapper配置文件中的resultMap标签内
对多 collection标签/ofType属性/property属性 Mapper配置文件中的resultMap标签内

动态语句

有些语句的条件是多变的 可能有些条件是不取值的,可以使用动态SQL来解决

if和where标签

<select id="selectEmployeeByCondition" resultType="employee">
select emp_id,emp_name,emp_salary from t_emp
<!-- where标签会自动去掉标签体内前面多余的and/or 如果没有条件吻合 就不加上where关键字 -->
<where>
<!-- 在if标签的test属性中,可以访问实体类的属性,不可以访问数据库表的字段 内写判断条件-->
<if test="empName != null">
<!-- 在if标签内部,需要访问接口的参数时还是正常写#{} -->
or emp_name=#{empName}
</if>
<if test="empSalary &gt; 2000">
or emp_salary>#{empSalary}
</if>
<!--
第一种情况:所有条件都满足 WHERE emp_name=? or emp_salary>?
第二种情况:部分条件满足 WHERE emp_salary>?
第三种情况:所有条件都不满足 没有where子句
-->
</where>
</select>

set标签

<update id="updateEmployeeDynamic">
update t_emp
<!-- set emp_name=#{empName},emp_salary=#{empSalary} -->
<!-- 使用set标签动态管理set子句,并且动态去掉两端多余的逗号 -->
<set>
<if test="empName != null">
emp_name=#{empName},
</if>
<!--&gt;是大于 &lt;是小于 -->
<!-- 也可以直接输入CD 然后在里面写入小于符号-->
<if test="empSalary &lt; 3000">
emp_salary=#{empSalary},
</if>
</set>
where emp_id=#{empId}
<!--
第一种情况:所有条件都满足 SET emp_name=?, emp_salary=?
第二种情况:部分条件满足 SET emp_salary=?
第三种情况:所有条件都不满足 update t_emp where emp_id=?
没有set子句的update语句会导致SQL语法错误
-->
</update>

trim标签(trim标签更灵活,可以用在任何有需要的地方)

<!-- List<Employee> selectEmployeeByConditionByTrim(Employee employee) -->
<select id="selectEmployeeByConditionByTrim" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id,emp_name,emp_age,emp_salary,emp_gender
from t_emp

<!-- prefix属性指定要动态添加的前缀 -->
<!-- suffix属性指定要动态添加的后缀 -->
<!-- prefixOverrides属性指定要动态去掉的前缀,使用“|”分隔有可能的多个值 -->
<!-- suffixOverrides属性指定要动态去掉的后缀,使用“|”分隔有可能的多个值 -->
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null">
emp_name=#{empName} and
</if>
<if test="empSalary &gt; 3000">
emp_salary>#{empSalary} and
</if>
<if test="empAge &lt;= 20">
emp_age=#{empAge} or
</if>
<if test="empGender=='male'">
emp_gender=#{empGender}
</if>
</trim>
</select>

choose/when/otherwise标签

<select id="selectEmployeeByConditionByChoose" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id,emp_name,emp_salary from t_emp
where
<choose>
<when test="empName != null">emp_name=#{empName}</when>
<when test="empSalary &lt; 3000">emp_salary &lt; 3000</when>
<otherwise>1=1</otherwise>
</choose>
<!--
从上到下依次执行条件判断
遇到的第一个满足条件的分支会被采纳
被采纳分支后面的分支都将不被考虑
如果所有的when分支都不满足,那么就执行otherwise分支
-->
</select>

foreach标签 (*)

<!--
例如批量插入用户或者批量删除用户
collection属性:要遍历的集合
item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
index属性:这里起一个名字,便于后面引用
遍历List集合,这里能够得到List集合的索引值
遍历Map集合,这里能够得到Map集合的key
-->
<foreach collection="empList" item="emp" separator="," open="values" index="myIndex">
<!-- 在foreach标签内部如果需要引用遍历得到的具体的一个对象,需要使用item属性声明的名称 -->
(#{emp.empName},#{myIndex},#{emp.empSalary},#{emp.empGender})
</foreach>

批量更新的时候要注意 在全局配置中添加allowMultiQueries=true

atguigu.dev.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true

如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用collection或list来引用这个list集合

<!-- int updateEmployeeBatch(@Param("empList") List<Employee> empList) -->
<update id="updateEmployeeBatch">
<foreach collection="empList" item="emp" separator=";">
update t_emp set emp_name=#{emp.empName} where emp_id=#{emp.empId}
</foreach>
</update>

抽取重复的SQL片段

<!-- 使用sql标签抽取重复出现的SQL片段 -->
<sql id="mySelectSql">
select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
</sql>

...

<!-- 使用include标签引用声明的SQL片段 -->
<include refid="mySelectSql"/>

拓展

资源创建的要求 Mapper接口要和Mapper配置文件名称一致 可以在sources下创建mapper接口包一致的文件夹结构存放mapperxml文件,最终打包位置一致,符合包扫描的mapper要求

PageHelper插件使用

导入依赖

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>

在 MyBatis 的配置文件中添加 PageHelper 的插件:其中,com.github.pagehelper.PageInterceptor 是 PageHelper 插件的名称,dialect 属性用于指定数据库类型(支持多种数据库)

<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>

页插件的使用

@Test
public void testTeacherRelationshipToMulti() {
TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);
PageHelper.startPage(1,2);
// 查询Customer对象同时将关联的Order集合查询出来
List<Teacher> allTeachers = teacherMapper.findAllTeachers();
PageInfo<Teacher> pageInfo = new PageInfo<>(allTeachers);
System.out.println("pageInfo = " + pageInfo);
long total = pageInfo.getTotal(); // 获取总记录数
System.out.println("total = " + total);
int pages = pageInfo.getPages(); // 获取总页数
System.out.println("pages = " + pages);
int pageNum = pageInfo.getPageNum(); // 获取当前页码
System.out.println("pageNum = " + pageNum);
int pageSize = pageInfo.getPageSize(); // 获取每页显示记录数
System.out.println("pageSize = " + pageSize);
List<Teacher> teachers = pageInfo.getList(); //获取查询页的数据集合
System.out.println("teachers = " + teachers);
teachers.forEach(System.out::println);
}

逆向工程和MybatisX插件

逆向工程只能生成单表crud的操作,多表查询依然需要我们自己编写

  1. 现在插件市场搜索 MyBatisX进行安装

  2. 然后连接数据库,在右侧的Database标签中添加Mysql数据库,填写相关信息

  3. 展开要展示的数据库,选中要显示的数据库,然后选中逆向工程的表右键使用MybatisX-Generator

-就会自动创建Mapper接口 Mapper配置文件和实体类了

SpringMVC

SpringMVC处理请求流程

  1. DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
  2. HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]
  3. HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理]
  4. Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]
  5. ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]

准备环境

导入依赖

<dependencies>
<!-- springioc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- web相关依赖 -->
<!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 -->
<!--
在 Spring Web MVC 6 中,Servlet API 迁移到了 Jakarta EE API,因此在配置 DispatcherServlet 时需要使用
Jakarta EE 提供的相应类库和命名空间。错误信息 “‘org.springframework.web.servlet.DispatcherServlet’
is not assignable to ‘javax.servlet.Servlet,jakarta.servlet.Servlet’” 表明你使用了旧版本的
Servlet API,没有更新到 Jakarta EE 规范。
-->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>${servlet.api}</version>
<scope>provided</scope>
</dependency>

<!-- springwebmvc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

</dependencies>

声明Controller

@Controller
public class HelloController {

//handlers
/**
* handler就是controller内部的具体方法
* @RequestMapping("/springmvc/hello") 就是用来向handlerMapping中注册的方法注解!
* @ResponseBody 代表向浏览器直接返回数据!
*/
@RequestMapping("/springmvc/hello")
@ResponseBody
public String hello(){
System.out.println("HelloController.hello");
return "hello springmvc!!";
}
}

声明springmvc设计组件信息的配置类

//TODO: SpringMVC对应组件的配置类 [声明SpringMVC需要的组件信息]

//TODO: 导入handlerMapping和handlerAdapter的三种方式
//1.自动导入handlerMapping和handlerAdapter [推荐]
//2.可以不添加,springmvc会检查是否配置handlerMapping和handlerAdapter,没有配置默认加载
//3.使用@Bean方式配置handlerMapper和handlerAdapter
@EnableWebMvc //加入 handlerMapping,handlerAdapter 给handlerAdapter配置json处理器
@Configuration
@ComponentScan("com.atguigu.controller") //TODO: 进行controller扫
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

@Bean
public HandlerMapping handlerMapping(){
return new RequestMappingHandlerMapping();
}

@Bean
public HandlerAdapter handlerAdapter(){
return new RequestMappingHandlerAdapter();
}

}

SpringMVC提供的接口,是替代web.xml的方案,更方便实现完全注解方式ssm处理!

//TODO: Springmvc框架会自动检查当前类的实现类,会自动加载 getRootConfigClasses / getServletConfigClasses 提供的配置类
//TODO: getServletMappings 返回的地址 设置DispatherServlet对应处理的地址
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

/**
* 指定service / mapper层的配置类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}

/**
* 指定springmvc的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { SpringMvcConfig.class };
}

/**
* 设置dispatcherServlet的处理路径!
* 一般情况下为 / 代表处理所有请求!
*/
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}

配置tomcat 然后在地址栏中输入路径访问hander

接收数据

违背请求方式,会出现405异常!!!

param:

  • 直接接收:handler(类型 形参名) 形参名 = 请求参数名
  • 注解指定 hander(@RequestParam(name=”请求参数名” ,required=true(是否必需传入),defaultValue=”默认值”))
  • 一名多值 hander(@RequestParam List key) 多值必须添加@RequestParam注解
  • 实体接收 hander(实体 对象) 对象属性名 = 请求参数名
@Controller
@RequestMapping("param")
public class ParamController {

/**
* 前端请求: http://localhost:8080/param/value?name=xx&age=18
*
* 可以利用形参列表,直接接收前端传递的param参数!
* 要求: 参数名 = 形参名
* 类型相同
* @return 返回前端数据
*/
//直接接值
@GetMapping(value="/value")
@ResponseBody
public String setupForm(String name,int age){
System.out.println("name = " + name + ", age = " + age);
return name + age;
}
//注解接值
@GetMapping(value="/data")
@ResponseBody
public Object paramForm(@RequestParam("name") String name, @RequestParam("stuAge") int age){
System.out.println("name = " + name + ", age = " + age);
return name+age;
}
//实体类接值 将请求参数name和age映射到实体类属性上!要求属性名必须等于参数名!否则无法映射!
@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody
public String addUser(User user) {
// 在这里可以使用 user 对象的属性来接收请求参数
System.out.println("user = " + user);
return "success";
}
}

路径参数:

  • 设置动态路径和标识 : /{key}/info/{key}
  • 接受路径 handler(@PathVariable(动态路径key) 类型 形参名)
 /**
* 动态路径设计: /user/{动态部分}/{动态部分} 动态部分使用{}包含即可! {}内部动态标识!
* 形参列表取值: @PathVariable Long id 如果形参名 = {动态标识} 自动赋值!
* @PathVariable("动态标识") Long id 如果形参名 != {动态标识} 可以通过指定动态标识赋值!
*
* 访问测试: /param/user/1/root -> id = 1 uname = root
*/
@GetMapping("/user/{id}/{name}")
@ResponseBody
public String getUser(@PathVariable Long id,
@PathVariable("name") String uname) {
System.out.println("id = " + id + ", uname = " + uname);
return "user_detail";
}

json

  • 数据接收 hander(@RequestBody 实体类 对象)

    需要导入jackson依赖和@EnableWebMvc

    导入jackson依赖

    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
    </dependency>
@PostMapping("/person")
@ResponseBody
public String addPerson(@RequestBody Person person) {

// 在这里可以使用 person 对象来操作 JSON 数据中包含的属性
return "success";
}

接收Cookie数据

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}

接收请求头数据

@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}

原生api对象操作

/**
* 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!
* 注意: 接收原生对象,并不影响参数接收!
*/
@GetMapping("api")
@ResponseBody
public String api(HttpSession session , HttpServletRequest request,
HttpServletResponse response){
String method = request.getMethod();
System.out.println("method = " + method);
return "api";
}

共享域

  1. 使用 Model 类型的形参

    @RequestMapping("/attr/request/model")
    @ResponseBody
    public String testAttrRequestModel(

    // 在形参位置声明Model类型变量,用于存储模型数据
    Model model) {

    // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
    // 存入请求域这个动作也被称为暴露到请求域
    model.addAttribute("requestScopeMessageModel","i am very happy[model]");

    return "target";
    }
  2. 使用 ModelMap 类型的形参

    @RequestMapping("/attr/request/model/map")
    @ResponseBody
    public String testAttrRequestModelMap(

    // 在形参位置声明ModelMap类型变量,用于存储模型数据
    ModelMap modelMap) {

    // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
    // 存入请求域这个动作也被称为暴露到请求域
    modelMap.addAttribute("requestScopeMessageModelMap","i am very happy[model map]");

    return "target";
    }
  3. 使用 Map 类型的形参

    @RequestMapping("/attr/request/map")
    @ResponseBody
    public String testAttrRequestMap(

    // 在形参位置声明Map类型变量,用于存储模型数据
    Map<String, Object> map) {

    // 我们将数据存入模型,SpringMVC 会帮我们把模型数据存入请求域
    // 存入请求域这个动作也被称为暴露到请求域
    map.put("requestScopeMessageMap", "i am very happy[map]");

    return "target";
    }
  4. 使用原生 request 对象

    @RequestMapping("/attr/request/original")
    @ResponseBody
    public String testAttrOriginalRequest(

    // 拿到原生对象,就可以调用原生方法执行各种操作
    HttpServletRequest request) {

    request.setAttribute("requestScopeMessageOriginal", "i am very happy[original]");

    return "target";
    }
  5. 使用 ModelAndView 对象

    @RequestMapping("/attr/request/mav")
    public ModelAndView testAttrByModelAndView() {

    // 1.创建ModelAndView对象
    ModelAndView modelAndView = new ModelAndView();
    // 2.存入模型数据
    modelAndView.addObject("requestScopeMessageMAV", "i am very happy[mav]");
    // 3.设置视图名称
    modelAndView.setViewName("target");

    return modelAndView;
    }
  6. Session级别属性(共享)域

@RequestMapping("/attr/session")
@ResponseBody
public String testAttrSession(HttpSession session) {
//直接对session对象操作,即对会话范围操作!
return "target";
}
  1. Application级别属性(共享)域
@Autowired
private ServletContext servletContext;

@RequestMapping("/attr/application")
@ResponseBody
public String attrApplication() {

servletContext.setAttribute("appScopeMsg", "i am hungry...");

return "target";
}

响应数据

混合开发

两种开发模式 前后端分离和jsp动态页面混合开发

混合开发

jsp依赖引入

<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>

jsp创建

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!-- 可以获取共享域的数据,动态展示! jsp== 后台vue -->
${msg}
</body>
</html>

配置jsp视图解析器

@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描
public class SpringMvcConfig implements WebMvcConfigurer {

//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/views/",".jsp");
}
}

handler返回视图


@GetMapping("jump")//跳转路径
public String jumpJsp(Model model){
System.out.println("FileController.jumpJsp");
model.addAttribute("msg","request data!!");
return "home";
}

转发和重定向

@RequestMapping("/redirect-demo")
public String redirectDemo() {
// 重定向到 /demo 路径
return "redirect:/demo";
}

@RequestMapping("/forward-demo")
public String forwardDemo() {
// 转发到 /demo 路径
return "forward:/demo";
}

//注意: 转发和重定向到项目下资源路径都是相同,都不需要添加项目根路径!填写项目下路径即可!

返回json数据

导入jackson依赖

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>

在配置类中添加json数据转换器(@EnableWebMvc)

@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller")

//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {
//..
}

要响应数据 序列化为XML或JSON格式的数据 , 并发送给客户端 要加入@ResponseBody注解,可以直接跳过视图解析器 直接返回给前端数据

@GetMapping("/accounts/{id}")
@ResponseBody
public Object handle() {
// ...
return obj;
}

如果类中每个方法上都标记了 @ResponseBody 注解,代表该类下的所有方法都生效,类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解

返回静态资源

我们在项目项目中添加了一个images 然后放入一张图片资源,但是我们在网址中无法直接访问到该图片,对于springmvc中 必须有相应的 @RequestMapping 才能找到处理请求的方法,所以访问不到该图片资源

我们可以在配置类中 开启静态资源处理,是专门用于处理静态资源请求的

@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller")
public class SpringMvcConfig implements WebMvcConfigurer {

@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/views/",".jsp");
}

//开启静态资源处理 <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}

再次请求时,就可以访问静态资源

RESTFul风格

客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;根据接口的具体动作,选择具体的HTTP协议请求方式,过去做增删改查操作需要设计4个不同的URL,现在一个就够了。

操作 传统风格 REST 风格
保存 /CRUD/saveEmp URL 地址:/CRUD/emp 请求方式:POST
删除 /CRUD/removeEmp?empId=2 URL 地址:/CRUD/emp/2 请求方式:DELETE
更新 /CRUD/updateEmp URL 地址:/CRUD/emp 请求方式:PUT
查询 /CRUD/editEmp?empId=2 URL 地址:/CRUD/emp/2 请求方式:GET

实例:

接口设计

功能 接口和请求方式 请求参数 返回值
分页查询 GET /user page=1&size=10 { 响应数据 }
用户添加 POST /user { user 数据 } {响应数据}
用户详情 GET /user/1 路径参数 {响应数据}
用户更新 PUT /user { user 更新数据} {响应数据}
用户删除 DELETE /user/1 路径参数 {响应数据}
条件模糊 GET /user/search page=1&size=10&keywork=关键字 {响应数据}
/**
* Controller
*/
import domain.Emp;
import domain.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("user")
public class UserController {
//分页查询
@GetMapping()
public List<User> page(@RequestParam(required = false ,defaultValue = "1") int page,
@RequestParam(required = false,defaultValue = "10") int size){
System.out.println("page" + page +"size"+size);
return null;
}
//用户添加
@PostMapping
public User addUser(@RequestBody User user){
return null;
}
//用户详情
@GetMapping("{id}")
public User selectUser(@PathVariable Integer id){
return null;
}
//用户更新
@PutMapping
public User put(@RequestBody User user){
return null;
}
//用户删除
@DeleteMapping("{id}")
public User delete(@PathVariable Integer id){
return null;
}
//条件模糊
@GetMapping("search")
public List<User> search(String keyword,@RequestParam(required = false ,defaultValue = "1") int page,
@RequestParam(required = false,defaultValue = "10") int size){
System.out.println("搜索内容"+keyword);
return null;
}
}

全局异常处理

声明式异常处理,新建异常处理控制类,统一定义异常处理handler方法

记得在配置类中确保异常处理控制类能被包扫描

package error;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* 全局异常处理
* 全局异常发生时,会走此类中的hander
* */
//@ControllerAdvice//可以返回逻辑视图 转发和重定向
@RestControllerAdvice //直接返回json字符串
public class GlobalExceptionHander {
//异常处理handler方法和普通的handler方法参数接收和响应都一致!
//指定异常的处理
@ExceptionHandler(ArithmeticException.class)
public Object ArithmeticExceptionHander(ArithmeticException er){
String message = er.getMessage();
System.out.println("message"+ message);
return null;
}
//父类(全局)异常的处理
@ExceptionHandler(Exception.class)
public Object ExceptionHander(Exception er){
String message = er.getMessage();
System.out.println("message"+ message);
return null;
}
}

拦截器

创建拦截器类

package interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
* 拦截器
* */
public class MyInterceptor implements HandlerInterceptor {
@Override
//执行hander之前调用的拦截方法 可以进行登陆保护,编码处理,权限处理等
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;//表示放行
}

@Override
//hander执行完之后触发 modelAndView 返回的视图和共享域数据对象 对敏感词语的处理
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override
//整体处理完之后触发 ex返回异常信息
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

配置详解

package config;

import interceptor.MyInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "interceptor")//TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/views/",".jsp");
}

//开启静态资源处理 <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new MyInterceptor());
}
//添加拦截器(精准配置)
//精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
//也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串
//registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
//添加配置其(排除配置)
//排除匹配,排除应该在匹配的范围内排除
//addPathPatterns("/common/request/one") 添加拦截路径
//excludePathPatterns("/common/request/tow"); 排除路径,排除应该在拦截的范围内
//registry.addInterceptor(new Process01Interceptor())
// .addPathPatterns("/common/request/one","/common/request/tow")
// .excludePathPatterns("/common/request/tow");
}

多个拦截器执行顺序

  1. preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
  2. postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
  3. afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。

参数校验

JSR 303 进行校验

注解 规则
@Null 标注值必须为 null
@NotNull 标注值不可为 null
@AssertTrue 标注值必须为 true
@AssertFalse 标注值必须为 false
@Min(value) 标注值必须大于或等于 value
@Max(value) 标注值必须小于或等于 value
@DecimalMin(value) 标注值必须大于或等于 value
@DecimalMax(value) 标注值必须小于或等于 value
@Size(max,min) 标注值大小必须在 max 和 min 限定的范围内
@Digits(integer,fratction) 标注值值必须是一个数字,且必须在可接受的范围内
@Past 标注值只能用于日期型,且必须是过去的日期
@Future 标注值只能用于日期型,且必须是将来的日期
@Pattern(value) 标注值必须符合指定的正则表达式
@Email 标注值必须是格式正确的 Email 地址
@Length 标注值字符串大小必须在指定的范围内
@NotEmpty 标注值字符串不能是空字符串
@Range 标注值必须在指定的范围内

首先导入依赖

<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>

<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>

直接在实体类中添加注解即可

handler标记和绑定错误信息

@RestController
@RequestMapping("user")
public class UserController {

/**
* @Validated 代表应用校验注解! 必须添加!
*/
@PostMapping("save")
public Object save(@Validated @RequestBody User user,
//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
BindingResult result){
//判断是否有信息绑定错误! 有可以自行处理!
if (result.hasErrors()){
System.out.println("错误");
String errorMsg = result.getFieldError().toString();
return errorMsg;
}
//没有,正常处理业务即可
System.out.println("正常");
return user;
}
}
  • @NotNull(包装类型不为null)

    若要对字符串校验,使用 @NotBlank 或 @NotEmpty 注解。

  • @NotEmpty (集合类型长度大于0)

    对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,对于其他类型此注解无效,需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。

  • @NotBlank (字符串,不为null,切不为” “字符串)

    @NotBlank 注解只能用于字符串类型的校验。

案例

导入依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ssm_integration_part</artifactId>
<groupId>com.jason.ssm</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ssm_integration_01</artifactId>
<packaging>war</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring.version>6.0.6</spring.version>
<jakarta.annotation-api.version>2.1.1</jakarta.annotation-api.version>
<jakarta.jakartaee-web-api.version>9.1.0</jakarta.jakartaee-web-api.version>
<jackson-databind.version>2.15.0</jackson-databind.version>
<hibernate-validator.version>8.0.0.Final</hibernate-validator.version>
<mybatis.version>3.5.11</mybatis.version>
<mysql.version>8.0.25</mysql.version>
<pagehelper.version>5.1.11</pagehelper.version>
<druid.version>1.2.8</druid.version>
<mybatis-spring.version>3.0.2</mybatis-spring.version>
<jakarta.servlet.jsp.jstl-api.version>3.0.0</jakarta.servlet.jsp.jstl-api.version>
<logback.version>1.2.3</logback.version>
<lombok.version>1.18.26</lombok.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--
需要依赖清单分析:
spring
ioc/di
spring-context / 6.0.6
jakarta.annotation-api / 2.1.1 jsr250
aop
spring-aspects / 6.0.6
tx
spring-tx / 6.0.6
spring-jdbc / 6.0.6

springmvc
spring-webmvc 6.0.6
jakarta.jakartaee-web-api 9.1.0
jackson-databind 2.15.0
hibernate-validator / hibernate-validator-annotation-processor 8.0.0.Final

mybatis
mybatis / 3.5.11
mysql / 8.0.25
pagehelper / 5.1.11

整合需要
加载spring容器 spring-web / 6.0.6
整合mybatis mybatis-spring x x
数据库连接池 druid / x
lombok lombok / 1.18.26
logback logback/ 1.2.3
-->
<dependencies>
<!--spring pom.xml依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta.annotation-api.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--
springmvc
spring-webmvc 6.0.6
jakarta.jakartaee-web-api 9.1.0
jackson-databind 2.15.0
hibernate-validator / hibernate-validator-annotation-processor 8.0.0.Final
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>${jakarta.jakartaee-web-api.version}</version>
<scope>provided</scope>
</dependency>

<!-- jsp需要依赖! jstl-->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>${jakarta.servlet.jsp.jstl-api.version}</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
</dependency>


<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>

<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<!--
mybatis
mybatis / 3.5.11
mysql / 8.0.25
pagehelper / 5.1.11
-->
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>

<!-- 整合第三方特殊依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>

<!-- 日志 , 会自动传递slf4j门面-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
</project>

添加实体类

@Data
public class Emp {
private Integer empId;
private String empName;
private double empSalary;
}

logback配置 : resources/logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置,ConsoleAppender表示输出到控制台 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>

<!-- 设置全局日志级别。日志级别按顺序分别是:TRACE、DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>

<!-- 根据特殊需求指定局部日志级别,可也是包名或全类名。 -->
<logger name="com.atguigu.mybatis" level="DEBUG" />

</configuration>

控制层配置编写(SpringMVC整合)

/**
* 控制层的配置类 (controller,springmvc)
* 1.controller
* 2.全局异常处理
* 3.handlerMapping handlerAdapter
* 4.静态资源处理
* 5.jsp视图解析器前后缀
* 6.json转化器
* 7.拦截器
* */
@EnableWebMvc
@Configuration
@ComponentScan()
public class WebMvcJavaConfig implements WebMvcConfigurer {
@Override
//静态资源处理
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

@Override
//jsp视图解析器
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views","jsp")
}

@Override
//拦截器
public void addInterceptors(InterceptorRegistry registry) {
//WebMvcConfigurer.super.addInterceptors(registry);
}
}

业务层配置类(service , aop ,tx)

/**
* 业务层配置类 service ,aop ,tx
*
* 1.service
* 2.开启aop注解的支持aspect
* 3.tx声明式食物管理:1.对应的事务管理器实现 2.开启事务注解支持
* */
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class ServiceJavaConfig {

public TransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
//注入数据库连接池
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}

持久层配置编写(MyBatis整合)

将SqlSessionFactory实例存储到IoC容器,将Mapper实例存储到IoC容器,mybatis提供了提供封装SqlSessionFactory和Mapper实例化的逻辑的FactoryBean组件,我们只需要声明和指定少量的配置即可!

准备数据库连接信息jdbc.properties

jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql:///mybatis-example
jdbc.driver=com.mysql.cj.jdbc.Driver

挖个坑,找不到路径 未解决 有时间来解决

SpringBoot3

快速入门

添加依赖,是一个组合包,已此项目为父工程,直接引用,不需要添加版本

<!--所有springboot项目都必须继承自 spring-boot-starter-parent-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>

添加web启动器

让Spring Boot帮我们完成各种自动配置,我们必须引入Spring Boot提供的自动配置依赖,我们称为启动器。因为我们是web项目,这里我们引入web启动器,在 pom.xml 文件中加入如下依赖:

<dependencies>
<!--web开发的场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

新建启动类 MainApplication.class

/**
* @SpringBootApplication是一个特殊的注解,用于标识一个Spring Boot应用程序的入口类。它的主要作用是将三个常用注解组合在一起,简化了配置的过程。
*
* 具体而言,@SpringBootApplication注解包含以下三个注解的功能:
* @Configuration:将该类标识为应用程序的配置类。它允许使用Java代码定义和配置Bean。
* @EnableAutoConfiguration:启用Spring Boot的自动配置机制。它根据项目的依赖项自动配置Spring应用程序的行为。自动配置根据类路径、注解和配置属性等条件来决定要使用的功能和配置。
* @ComponentScan:自动扫描并加载应用程序中的组件,如控制器、服务、存储库等。它默认扫描@SpringBootApplication注解所在类的包及其子包中的组件。
*
* 使用@SpringBootApplication注解,可以将上述三个注解的功能集中在一个注解上,简化了配置文件的编写和组件的加载和扫描过程。它是Spring Boot应用程序的入口点,标识了应用程序的主类,
* 并告诉Spring Boot在启动时应如何配置和加载应用程序。
*/
@SpringBootApplication
public class MainApplication {

//SpringApplication.run() 方法是启动 Spring Boot 应用程序的关键步骤。它创建应用程序上下文、
// 自动配置应用程序、启动应用程序,并处理命令行参数,使应用程序能够运行和提供所需的功能
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}

SpringBoot工程下,进行统一的配置管理,你想设置的任何参数(端口号、项目根路径、数据库连接信息等等)都集中到一个固定位置和命名的配置文件(application.propertiesapplication.yml)中!配置文件放在src/main/resources中,如果同时存在application.properties | application.yml(.yaml) , properties的优先级更高。

常用yaml后缀的配置文件

例如

spring:
jdbc:
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql:///springboot_02
username: root
password: root

server:
port: 80

application.yaml(开发)

spring:
jdbc:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///dev
username: root
password: root

application-test.yaml(测试)

spring:
jdbc:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///test
username: root
password: root

application-prod.yml(生产)

spring:
jdbc:
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///prod
username: root
password: root

环境激活

spring:
profiles:
active: dev

如果设置了spring.profiles.active,并且和application有重叠属性,以active设置优先。

如果设置了spring.profiles.active,和application无重叠属性,application设置依然生效!

读取配置文件

package com.atguigu.properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class DataSourceProperties {

@Value("${spring.jdbc.datasource.driverClassName}")
private String driverClassName;
@Value("${spring.jdbc.datasource.url}")
private String url;
@Value("${spring.jdbc.datasource.username}")
private String username;
@Value("${spring.jdbc.datasource.password}")
private String password;
// 生成get set 和 toString方法
//...
}

在controller中注入然后测试

@Autowired
private DataSourceProperties dataSourceProperties ;

@RequestMapping(path = "/hello")
public String sayHello() {
System.out.println(dataSourceProperties);
return "Hello Spring Boot ! " ;
}

加入@@ConfigurationProperties(prefix = “spring.jdbc.datasource”),读取属性文件中前缀为spring.jdbc.datasource的值。前缀和属性名称和配置文件中的key必须要保持一致才可以注入成功

测试批量配置文件注入

@RestController
public class HelloController {

@Autowired
private DataSourceProperties dataSourceProperties;
@Autowired
private DataSourceConfigurationProperties dataSourceConfigurationProperties;

@GetMapping("/hello")
public String hello(){
System.out.println("dataSourceProperties = " + dataSourceProperties);
System.out.println("dataSourceConfigurationProperties = " + dataSourceConfigurationProperties);
return "Hello,Spring Boot 3!";
}
}

SpringBoot3整合SpringMVC

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>

<groupId>org.example</groupId>
<artifactId>springboot_druid01</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>


<dependencies>
<!-- web开发的场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 数据库相关配置启动器 jdbctemplate 事务相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- druid启动器的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.18</version>
</dependency>

<!-- 驱动类-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>

</dependencies>

</project>

创建启动类和实体类 l略

创建application.yml

# web相关的配置
# https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server
server:
# 端口号设置
port: 80
# 项目根路径
servlet:
context-path: /boot
  1. server.servlet.context-path 设置上下文路径

  2. spring.mvc.view.prefixspring.mvc.view.suffix: 这两个属性用于配置视图解析器的前缀和后缀

  3. spring.resources.static-locations: 配置静态资源的位置。

  4. spring.http.encoding.charsetspring.http.encoding.enabled: 这两个属性用于配置HTTP请求和响应的字符编码 ebabled用于启用或禁用字符编码的自动配置

创建Controller

@Slf4j
@Controller
@RequestMapping("emp")

public class EmpController {

@Autowired
private JdbcTemplate jdbcTemplate;

@GetMapping("byid")
@ResponseBody
public Emp selectById(){
String sql = "select * from employee where ecode = ?";
Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class),1);
log.info("查询的emp数据为:",emp.toString());
return emp;
}
}

静态资源访问

定义静态资源默认查找路径

package org.springframework.boot.autoconfigure.web;
//..................
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
private String[] staticLocations;
private boolean addMappings;
private boolean customized;
private final Chain chain;
private final Cache cache;

public Resources() {
this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
this.addMappings = true;
this.customized = false;
this.chain = new Chain();
this.cache = new Cache();
}

也可以在application.yaml中配置静态资源地址

spring:
web:
resources:
# 配置静态资源地址,如果设置,会覆盖默认值
static-locations: classpath:/webapp

自定义拦截器

拦截器声明

@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor拦截器的preHandle方法执行....");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor拦截器的postHandle方法执行....");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor拦截器的afterCompletion方法执行....");
}
}

拦截器配置,配置类要在启动类的同包或者子包方可生效

@Configuration
public class MvcConfig implements WebMvcConfigurer {

@Autowired
private MyInterceptor myInterceptor ;

/**
* /** 拦截当前目录及子目录下的所有路径 /user/** /user/findAll /user/order/findAll
* /* 拦截当前目录下的以及子路径 /user/* /user/findAll
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("/**");
}
}

SpringBoot3整合Druid数据源

添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>
<groupId>com.atguigu</groupId>
<artifactId>springboot-starter-druid-04</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- web开发的场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 数据库相关配置启动器 jdbctemplate 事务相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- druid启动器的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.18</version>
</dependency>

<!-- 驱动类-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>

</dependencies>

<!-- SpringBoot应用打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
```

创建启动类 创建实体类-

添加druid连接池配置

spring:
datasource:
# 连接池类型
type: com.alibaba.druid.pool.DruidDataSource
# Druid的其他属性配置 springboot3整合情况下,数据库连接信息必须在Druid属性下!
druid:
url: jdbc:mysql://localhost:3306/yggl?zeroDateTimeBehavior=convertToNull
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 初始化时建立物理连接的个数
initial-size: 5
# 连接池的最小空闲数量
min-idle: 5
# 连接池最大连接数量
max-active: 20
# 获取连接时最大等待时间,单位毫秒
max-wait: 60000
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
test-while-idle: true
# 既作为检测的间隔时间又作为testWhileIdel执行的依据
time-between-eviction-runs-millis: 60000
# 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接(配置连接在池中的最小生存时间)
min-evictable-idle-time-millis: 30000
# 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual)
validation-query: select 1
# 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-borrow: false
# 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-return: false
# 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
pool-prepared-statements: false
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
max-pool-prepared-statement-per-connection-size: -1
# 合并多个DruidDataSource的监控数据
use-global-data-source-stat: true

创建EmpController

@Slf4j
@Controller
@RequestMapping("emp")

public class EmpController {

@Autowired
private JdbcTemplate jdbcTemplate;

@GetMapping("byid")
@ResponseBody
public Emp selectById(){
String sql = "select * from employee where ecode = ?";
Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class),1);
log.info("查询的emp数据为:",emp.toString());
return emp;
}
}

启动测试后 发现Druid虽然适配了SpringBoot3 但是缺少自动装配的配置文件 ,需要手动在resources目录下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure

SpringBoot3整合Mybatis

导入依赖

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>

<!-- 数据库相关配置启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- druid启动器的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.18</version>
</dependency>

<!-- 驱动类-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>

</dependencies> `

配置文件application.yaml

server:
port: 80
servlet:
context-path: /
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:mysql:///day01
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
configuration: # setting配置
auto-mapping-behavior: full
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
type-aliases-package: com.atguigu.pojo # 配置别名
mapper-locations: classpath:/mapper/*.xml # mapperxml位置

创建实体类

创建Mapper接口,在UserMapper.xml中实现sql语句

创建三层

controller

@Slf4j
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;

@GetMapping("/list")
@ResponseBody
public List<User> getUser(){
List<User> userList = userService.findList();
log.info("查询的user数据为:{}",userList);
return userList;
}

service

@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;

public List<User> findList(){
List<User> users = userMapper.queryAll();
log.info("查询全部数据:{}",users);
return users;
}
}

创建启动类和接口扫描

@MapperScan("com.atguigu.mapper") //mapper接口扫描配置
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}

声明式事务整合配置

依赖导入

 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

只需在方法(或者类)加上 @Transactional 注解,就自动纳入 Spring 的事务管理了

AOP整合

依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

直接使用aop注解即可

@Component
@Aspect
public class LogAdvice {

@Before("execution(* com..service.*.*(..))")
public void before(JoinPoint joinPoint){
System.out.println("LogAdvice.before");
System.out.println("joinPoint = " + joinPoint);
}

}

Mybatis-Plus

就是mybatis的增强版

快速入门

准备数据

CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

导入对应依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.0.5</version>
</parent>

<groupId>com.jason</groupId>
<artifactId>mybatis_plus01</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- 测试环境 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>

<!-- 数据库相关配置启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- druid启动器的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.18</version>
</dependency>

<!-- 驱动类-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>

</dependencies>
<!-- SpringBoot应用打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

新建连接池配置和自动装配文件 (自动装配在上面有)

# 连接池配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:mysql:///mybatis_plus
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

创建com.jason的包(自定义包)编写实体类和启动类

@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
@MapperScan("com.jason.mapper")
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}

然后再test中创建同样的包 ,测试查询所有的信息

@org.springframework.boot.test.context.SpringBootTest
public class SpringBootTest {
@Autowired
private UserMapper userMapper;
@Test
public void testquery(){
List<User> userList = userMapper.selectList(null);//先不设置tiao'jian
for (User user : userList) {
System.out.println(user);
}
}
}

可以在application.yaml中配置mybatis-plus mapperxml的地址

mybatis-plus: # mybatis-plus的配置
# 默认位置 private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};
mapper-locations: classpath:/mapper/*.xml

insert (插入记录),delete(删除),deleteByMap(),select方法,update

// T 就是要插入的实体对象
int insert(T entity); //插入记录

// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

基于Service接口CRUD

通用 Service CRUD 封装[IService ](https://gitee.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/service/IService.java “IService (opens new window)”接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,

比mapper接口添加了批量方法,自动添加事务

接口继承IService接口

public interface UserService extends IService<User> {
}

实现类继承ServiceImpl实现类,一半的抽象方法在Iservice中还有一半在ServiceImpl中,所以要继承一个接口+一个类

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{ }

具体方法

保存:
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

修改或者保存:
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

移除:
// 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

更新:
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改.0
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

数量:
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

查询:
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

集合:
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

分页

在MainApplication中导入分类插件

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//mybatis-plus插件集合
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

使用分页查询

@Test
public void testPageQuery(){
//设置分页参数
Page<User> page = new Page<>(1, 5);
userMapper.selectPage(page, null);
//获取分页数据
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}

自定义mapper方法使用分页

mapper中添加方法

IPage<User> selectPageVo(IPage<User> page,@Param("id") Integer id);

mapper.xml

<mapper namespace="com.jason.mapper.UserMapper">
<select id="selectPageVo" resultType="user">
select * from user where id > #{id}
</select>
</mapper>

application.yaml中配置别名和信息

# 连接池配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:mysql:///mybatis_plus
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
# 默认mapper.xml文件 指定的位置mapper文件夹
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台输出sql语句
type-aliases-package: com.jason.pojo # 设置别名

Test中添加测试

@Test
public void testQuick(){
Page page = new Page(1, 2);
userMapper.selectPageVo(page ,2);
System.out.println("Total:"+page.getTotal());
}

条件构造器

自定义限制条件,来选择对应的数据,可以构建灵活、高效的查询条件,而不需要手动编写复杂的 SQL 语句,但是条件很复杂的时候,还是要用sql。

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John"); // 添加等于条件
queryWrapper.ne("age", 30); // 添加不等于条件
queryWrapper.like("email", "@gmail.com"); // 添加模糊匹配条件
等同于:
delete from user where name = "John" and age != 30
and email like "%@gmail.com%"
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

查询条件

@Test
public void test01(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "a")
.between("age", 20, 30)
.isNotNull("email");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

排序条件

@Test
public void test02(){
//按年龄降序查询用户,如果年龄相同则按id升序排列
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.orderByDesc("age")
.orderByAsc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}

and和or的使用

@Test
public void test04() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
//UPDATE t_user SET age=?, email=? WHERE username LIKE ? AND age > ? OR email IS NULL)
queryWrapper
.like("username", "a")
.gt("age", 20)
.or()
.isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("user@atguigu.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("受影响的行数:" + result);
}

使用condition判断

queryWrapper.eq(!StringUtils.isEmpty(name),"name",name)
.eq(age>1,"age",age);

updateWrapper

@Test
//使用updateWrapper可以随意设置列的值!!
public void testQuick2(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
//将id = 3 的email设置为null, age = 18
updateWrapper.eq("id",3)
.set("email",null) // set 指定列和结果
.set("age",18);
//如果使用updateWrapper 实体对象写null即可!
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);

}

LambdaQueryWrapper

//QueryWrapper
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John")
.ge("age", 18)
.orderByDesc("create_time")
.last("limit 10");
List<User> userList = userMapper.selectList(queryWrapper);
//LambdaQueryWrapper
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();

lambdaQueryWrapper.eq(User::getName, "John")
.ge(User::getAge, 18)
.orderByDesc(User::getCreateTime)
.last("limit 10");
List<User> userList = userMapper.selectList(lambdaQueryWrapper);

注解

@TableName注解

//表名注解,表示实体类对应的表
@TableName("t_user")//对应数据库表名
public class User {
// ...
}

@TableId 注解

@TableName("sys_user")
public class User {
@TableId(value="主键列名",type=主键策略)
//...
}
属性 类型 必须指定 默认值 描述
value String “” 主键字段名
type Enum IdType.NONE 指定主键类型
IdType属性可选值:
描述
AUTO 数据库 ID 自增 (mysql配置主键自增长)
ASSIGN_ID(默认) 分配 ID(主键类型为 Number(Long )或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
全局配置修改主键策略:

application.yaml配置

mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto

在以下场景下,添加@TableId注解是必要的:

  1. 实体类的字段与数据库表的主键字段不同名:如果实体类中的字段与数据库表的主键字段不一致,需要使用@TableId注解来指定实体类中表示主键的字段。
  2. 主键生成策略不是默认策略:如果需要使用除了默认主键生成策略以外的策略,也需要添加@TableId注解,并通过value属性指定生成策略。

@TableFiled 字段注解

@TableName("sys_user")
public class User {
@TableId
private Long id;
@TableField("nickname")
private String name;
private Integer age;
private String email;
}

插件

逻辑删除实现, 平常的delete删除是物理删除,会对数据库中的数据进行直接删除。逻辑删除是吧数据库中的该字段或表变成被删除状态,数据库中仍能看到这个数据

实现:

表中添加删除字段

ALTER TABLE USER ADD deleted INT DEFAULT 0 ;  # int 类型 1 逻辑删除 0 未逻辑删除

在实体类中指定逻辑删除字段和属性值

单一指定

@Data
public class User {
// @TableId
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic
//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 0
private Integer deleted;
}

全局指定

mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

加入逻辑删除以后,删除语句实质为修改语句

//逻辑删除
@Test
public void testQuick5(){
//逻辑删除
userMapper.deleteById(5);
}
//查询
@Test
public void testQuick6(){
//正常查询.默认查询非逻辑删除数据
userMapper.selectList(null);
}
//SELECT id,name,age,email,deleted FROM user WHERE deleted=0

乐观锁实现

悲观锁: 在数据访问的过程中,将共享资源锁定,阻塞其他线程的访问,当完成操作时,才释放锁,但在高并发环境下,效率低

乐观锁:在数据访问时不加锁,记录版本信息,如果该资源的版本和之前读取的版本不一致,说明其他线程修改了该资源,进行冲突处理,如果一致,则进行更新操作。

实现流程:更新时,检查版本号是不是数据库当前的版本号,如果一致,执行更新,version+=1; 如果version不对,更新失败

使用mybatis-plus使用乐观锁

添加版本号更新插件

//乐观锁插件(版本号) 在更新的时,自动对比版本号和版本号+1
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

在数据库中添加version字段

ALTER TABLE USER ADD VERSION INT DEFAULT 1 ;  # int 类型 乐观锁字段

在实体类中属性中添加@Version注解

@Version
private Integer version;

测试

//演示乐观锁生效场景
@Test
public void testQuick7(){
//步骤1: 先查询,在更新 获取version数据
//同时查询两条,但是version唯一,最后更新的失败
User user = userMapper.selectById(5);
User user1 = userMapper.selectById(5);

user.setAge(20);
user1.setAge(30);

userMapper.updateById(user);
//乐观锁生效,失败!
userMapper.updateById(user1);
}

防全表更新和删除实现

启动类添加防止全表更新和删除拦截器插件

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//mybatis-plus插件集合
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//乐观锁插件(版本号) 在更新的时,自动对比版本号和版本号+1
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//防止全表更新和全表删除的插件,再进行上述操作时会进行拦截
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}

测试

@Test
public void testQuick8(){
User user = new User();
user.setName("custom_name");
user.setEmail("xxx@mail.com");
//Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
//全局更新,报错
userService.saveOrUpdate(user,null);
}

也可使用mybatisX来实现逆向工程快速生成 与Myabtis 逆向工程同理

微头条案例

数据库数据导入

CREATE DATABASE sm_db;

USE sm_db;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

DROP TABLE IF EXISTS `news_headline`;
CREATE TABLE `news_headline` (
`hid` INT NOT NULL AUTO_INCREMENT COMMENT '头条id',
`title` VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '头条标题',
`article` VARCHAR(5000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '头条新闻内容',
`type` INT NOT NULL COMMENT '头条类型id',
`publisher` INT NOT NULL COMMENT '头条发布用户id',
`page_views` INT NOT NULL COMMENT '头条浏览量',
`create_time` DATETIME(0) NULL DEFAULT NULL COMMENT '头条发布时间',
`update_time` DATETIME(0) NULL DEFAULT NULL COMMENT '头条最后的修改时间',
`version` INT DEFAULT 1 COMMENT '乐观锁',
`is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除 0 未删除',
PRIMARY KEY (`hid`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (1, '特色产业激发乡村振兴新活力', '推进中国式现代化,必须全面推进乡村振兴。习近平总书记指出,产业振兴是乡村振兴的重中之重,也是实际工作的切入点。近日,记者走进乡村一线,看到各地以特色产业为抓手,拓展产业链发展产业集群,一二三产业融合发展,培育乡村振兴新动能。\n\n  这个端午,广东茂名高州市根子镇柏桥村的荔枝迎来了丰收。今年4月,习近平总书记来到柏桥村考察调研。总书记走进荔枝种植园,了解当地发展特色种植产业和文旅产业等情况,并同现场技术人员亲切交流。', 1, 1, 0, '2023-06-25 09:26:20', '2023-06-25 09:26:20', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (2, '北京连续三天最高温超40℃,6月“炎值”因何爆表?', ' 中新社北京6月24日电 (记者 陈杭 徐婧)京城连续三日“热晴不减”,且高温红色预警持续生效。截至24日13时51分,作为北京地区气象观测代表站的南郊观象台气温突破40℃,这是该站观测史上首次连续三天气温超40℃。22日以来,北京高温“烤验”突出。22日,北京南郊观象台最高气温达41.1℃,这是有观测纪录以来历史第二高(并列)。北京市气象局表示,观象台1951年建站以来极端最高气温为41.9℃,出现在1999年7月24日。\n\n  23日,北京南郊观象台最高气温为40.3℃,这是该观象台建站以来首次出现连续两天最高气温超40℃。当天,北京时隔9年再次发布最高级别的高温红色预警信号。', 1, 1, 0, '2023-06-25 09:28:06', '2023-06-25 09:28:06', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (3, '今年夏天,极端高温是否会成为常态?', '针对京津冀地区持续高温天气,23日下午,中国气象局召开高温天气新闻通气会。\n\n  刚过6月就出现极端高温天,今年夏天还会有多少高温天呢?对此,国家气候中心首席预报员高辉表示,根据国家气候中心预计,今年夏天全国大部分地区气温都比常年同期要偏高,这也对应着高温日数也要高于常年同期。但不同的地区,高温集中时段不一样,比如南方地区是在盛夏时间段进入高温季,而北方地区往往是在初夏时间段,所以从今年夏季来说,要区分不同的地区来考虑高温的影响。\n\n  我国各地高温集中时段有明显的地域差异。对华北地区来说,通常雨季前的6月至7月初更容易出现高温天气,连续数天的高温在6月也比较常见。高辉说,这段时间主要是干热型高温为主,表现为气温高湿度小。进入7月后期,随着副高北跳和夏季风往北推进,水汽输送和大气湿度增加,云量也会增多,会出现闷热天气,也就是湿热型高温。就最高气温而言,前一时段气温最高值通常高于后一时段。但也需要说明的是,人体体感温度不仅和气温有关,还受到湿度影响,往往这种湿热型高温会加重人体体感温度。', 1, 1, 0, '2023-06-25 09:31:00', '2023-06-25 09:31:00', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (4, '中央气象台发布今年首个高温橙色预警', '新华社北京6月22日电(记者黄垚)22日18时,中央气象台升级发布今年首个高温橙色预警。预计23日白天,华北、黄淮等地将继续出现35℃以上的高温天气,北京、天津、河北中南部、山东中北部等地部分地区最高气温可达40℃左右。\n\n  气象监测显示,22日8时至16时,北京、天津、河北中部、山东北部等地气温上升迅猛,最高气温升至40℃以上。上述4省份共有17个国家气象观测站最高气温突破历史极值。', 1, 1, 0, '2023-06-25 09:31:36', '2023-06-25 09:31:36', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (5, '江南水乡 龙舟竞渡', '江南水乡 龙舟竞渡---6月18日,浙江省湖州市“我们的节日·端午”暨第七届江南·民当端午民俗文化旅游节在南浔区和孚镇民当村开幕,来自南浔区各个乡镇的农民选手在河道中赛龙舟、划菱桶,体验传统端午民俗。', 1, 1, 0, '2023-06-25 09:32:13', '2023-06-25 09:32:13', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (6, '螃蟹粽、印花蛋、艾草凉粉……你知道端午有哪些创意美食吗?', '端午有旅行路上的见闻,有诗画里的艺术,也少不了舌尖上的风韵。听风入夏粽香佐茶,您还知道端午有哪些创意美食吗?端午至味,总少不了粽子这一味。甜的、咸的,肉馅的、蛋黄的、红枣的、豆沙的……一起来寻味端午!\n\n  古人其实早就喜欢把各种果干放进粽子里,美食家苏轼还发明了杨梅粽。《玉台新咏》中说,“酒中喜桃子,粽里觅杨梅。”后来苏轼曾借用过这个典故,在元祐三年所写的端午帖子中说,“不独盘中见卢橘,时于粽里得杨梅”。', 1, 1, 0, '2023-06-25 09:32:40', '2023-06-25 09:32:40', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (7, '尼克斯拒绝执行罗斯球队选项 罗斯成自由球员', '北京时间6月25日,据多方消息源报道,尼克斯拒绝执行德里克-罗斯下赛季的球队选项,罗斯成为完全自由球员。\n\n  34岁的罗斯在刚刚结束的赛季队内角色严重下滑,他仅出战27场比赛,场均登场12.5分钟,得到5.6分1.5篮板1.7助攻。\n\n  2021年,罗斯与尼克斯签下3年4300万美元的续约合同,其中最后一年为1560万美元球队选项。', 2, 2, 0, '2023-06-25 09:34:26', '2023-06-25 09:34:26', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (8, '班凯罗承诺代表美国男篮打世界杯 名单仅差1人', '北京时间6月25日,据著名NBA记者沙姆斯-查拉尼亚报道,魔术前锋保罗-班凯罗承诺将代表美国男篮参加2023年男篮世界杯。\n\n  班凯罗在刚刚结束的赛季场均能够砍下20.0分6.9篮板3.7助攻,获得了NBA2022-23赛季年度最佳新秀。', 2, 2, 0, '2023-06-25 09:34:59', '2023-06-25 09:34:59', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (9, 'F1加拿大大奖赛正赛:维斯塔潘冠军 阿隆索亚军', '2023年F1加拿大大奖赛正式比赛结束。红牛车队维斯塔潘杆位发车一路轻松领跑,再次完成了Pole-to-Win!这是红牛车队历史上的第100座分站冠军!同时也是维斯塔潘F1生涯的第41座分站冠军,追平了“车神”埃尔顿·塞纳的冠军数!阿斯顿马丁车队阿隆索亚军,梅赛德斯车队汉密尔顿季军。', 2, 2, 0, '2023-06-25 09:35:43', '2023-06-25 09:35:43', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (10, 'CTCC绍兴柯桥站圆满落幕 张志强曹宏炜各取一冠', '6月24日,2023赛季CTCC中国汽车场地职业联赛绍兴柯桥站在雨中的浙江国际赛车场上演了两回合决赛的巅峰角逐。在线上线下观众的共同见证下,超级杯-TCR中国系列赛、运动杯-长三角赛车节联袂献上高水平对决,以精彩的比赛献礼这个端午节假期!TCR 中国系列赛第三回合于今天上午率先开战。来自壳牌捷凯领克车队的张志强穿云破雾夺得冠军;夺得该回合亚军的是驾驶新赛车出战的东风本田车手高度,季军则由Z.SPEED N车队的张臻东斩获。这也是超级杯四冠王本赛季首次登台。', 2, 2, 0, '2023-06-25 09:36:18', '2023-06-25 09:36:18', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (11, '国象联赛常规赛收兵:杭州银行第一 山东成功上岸', '6月17日,“武陵山大裂谷杯”中国国际象棋甲级联赛常规赛在武陵云海国际酒店进行了最后一轮的争夺,杭州银行弈和山东队,抢到常规赛的冠军;山东队也是凭借这场平局,成功脱离保级区。本轮最大的悬念是第八名的争夺——在年底进行的甲级联赛总决赛中,前八名为上半区争冠组,保级无忧;而第九至十二名为保级区,不仅夺冠无望,还要为保级而苦战。', 2, 2, 0, '2023-06-25 09:36:51', '2023-06-25 09:36:51', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (12, '围棋名宿解读高考作文:人生如棋 要先学会下“本手”', '今年高考开考了,在语文考试后,体育借势冲上了社交媒体的热搜榜。奥运相关话题进入高考,是意料之中。不过当记者看到关于围棋术语“本手、妙手和俗手”的作文命题时,着实觉得有些难。在被迅速刷屏的朋友圈里,记者感受到了很多从业者的激动、兴奋乃至油然而生的自豪感。但也有人则为那些没学过棋的孩子感到担心,这么难的题目,究竟该如何解题?\n\n  “围棋正在深入人心。题目有些难,‘俗手’如何定义?但确实应该先下好‘本手’。”翻到中国围棋协会副主席、国家围棋队领队华学明的这条朋友圈动态时,记者瞬间觉得这道公认的难题有了解题的思路。正如高考作文材料中所说,本手是基础。只有持之以恒地打好基础,补强短板,守住不发生系统性风险的底线,才有可能在本手的基础上,下出妙手,避免俗手。而如果脱离了基础,所谓的妙手很可能就是花拳绣腿,经不起推敲,更经不起对手的冲击。世界冠军柯洁表示:“很多人在对局中经常会拘泥于局部,下出假妙手。想下出真正的妙手,必须在平日里有一定的经验积累和训练,才可能完成真正卓越的妙手。”人生如棋,棋如人生。“其实人生中大部分时间都是在下本手”,围棋名宿曹大元九段说。', 2, 2, 0, '2023-06-25 09:37:43', '2023-06-25 09:37:43', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (13, '不甘人后:被生成式AI弥漫的亚马逊', '今年早些时候,随着ChatGPT席卷全球,亚马逊的经理们要求员工开动脑筋,想想如何使用人工智能(AI)聊天机器人技术来改进自家产品和工作流程。\n\n  其中一些想法被分享在一份名为《生成式AI——ChatGPT的影响和机会分析》的内部文件中。这份文件共列了ChatGPT和类似应用程序在亚马逊多个团队中的67个潜在应用案例。\n\n  早在20世纪90年代,亚马逊就靠在网上卖书创造了互联网界首个真正的商业奇迹。\n\n  随后,Kindle阅读器带来革命性体验,Alexa和Echo智能音箱又带来了语音计算,而AWS则创造了云计算行业,ChatGPT就运行在这个行业之上。\n\n  但这次热潮中拿到先发优势的是同为科技大厂的微软。微软现在是OpenAI背后的金主,且还在忙着把ChatGPT的底层技术融进微软产品和服务中。', 4, 5, 0, '2023-06-25 09:40:20', '2023-06-25 09:40:20', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (14, '微创新超实用:米家旅行箱居然想到了这一点', '旅行说走就走,除非老板没安排。名义上是旅游,实则执行任务,对内讲“为公司负重前行”,对外称“带薪游山玩水”,一介打工人,两副扑克脸,个中苦乐谁人知!\n\n“差旅人”精明如我,随身携带更偏向实用。\n\n必备日用之外,能路上买的尽量不带,华而不实的东西,往包里多塞一个都算我输。行李箱尺寸自然也要浓缩到小巧但够装的20英寸,拉着轻松又顺手,常用小物件转移到背包,“轻装上战场”。', 4, 5, 0, '2023-06-25 09:41:04', '2023-06-25 09:41:04', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (15, '小鹏G6动态试驾:辅助驾驶很惊喜', '这次我们开着小鹏G6上了赛道,又体验了最新版本的高速NGP和城市NGP,小鹏,还顺便测了下充电速度,那么小鹏G6驾驶感受如何?辅助驾驶表现怎么样?', 4, 5, 0, '2023-06-25 09:42:07', '2023-06-25 09:42:07', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (16, '养车市场陷入低价内卷,“虎猫狗”还没等到春天', '今年“618”期间,汽车后市场的玩家们都打出了“低价牌”。比如途虎养车宣布推出“6.18全民养车季”活动,在此期间北京车主可享受“轮胎买一送一”以及多品牌轮胎降价促销的活动。\n\n  与此同时,京东养车和天猫养车两大大厂玩家,在本次618期间也喊出了各自的营销口号。\n\n  前者不仅喊出了“养车爱车立省不止30%”的口号,还推出了轮胎、保养买贵赔两倍、“轮胎免费装、三年无忧质保”、5公里无服务门店赔双倍安装费等举措;天猫养车的618活动,则覆盖了更大的零部件范围,比如推出了空调清洗、机油和轮胎更换等低价服务。\n\n  这样看,在本次618期间,途虎养车、京东养车和天猫养车均贯彻着“以价换量”的战略,以至于让行业价格战一触即发。这些玩家会这样做,主要是为了与传统4S店、以及与彼此竞争,以便保证自身获得更多的市场份额。', 4, 5, 0, '2023-06-25 09:42:51', '2023-06-25 09:42:51', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (17, '微软股价历史新高 聊天机器人技术潜力显现', '周四,微软股价创下历史新高,成为今年继英伟达和苹果之后,又一家市值达到新高点的大型科技公司。这家软件巨头正致力于在其产品和服务中添加生成式人工智能功能,旨在全面改造其Office产品阵容,其中包括Excel、PowerPoint、Outlook和Word等。股价上涨3.2%,收于每股348.1美元,为2021年11月19日以来的最高收盘价。自今年初起,微软股价累计上涨了45%,市值增加约8006亿美元。微软持有OpenAI的大部分股份,这家初创公司凭借聊天机器人ChatGPT引发生成式人工智能的热潮。近几个月来,该工具广受欢迎,展示了聊天机器人技术所具有的巨大潜力。微软于今年1月宣布将再向OpenAI投资100亿美元。然而有报道称,微软与OpenAI之间既有合作,亦存竞争,这种特殊的双重关系导致了双方关系的紧张和潜在冲突。科技股如英伟达等同样受益于生成式人工智能技术的应用,各公司将此技术融入各自产品,进而推动相关芯片需求。英伟达股价今年已飙升192%,被视为最大赢家。', 4, 5, 0, '2023-06-25 09:43:48', '2023-06-25 09:43:48', 0);
INSERT INTO `news_headline` (hid,title,article,TYPE,publisher,page_views,create_time,update_time,is_deleted) VALUES (18, '再获11亿美元投资:蔚来“长期主义”的底气', '如果说全系降价3万是李斌的“阳谋”,那么蔚来ET5T的发布,则是李斌的又一次诚意之作。\n\n  ET5T是蔚来首款售价下探到30万元以下的新车,作为ET5的姐妹车型,ET5T和ET5的双车合璧,得以在30万以下快速开疆辟土。\n\n  这样的做法有迹可循:特斯拉曾经在Model Y上实践过,并大获成功。\n\n  Model Y和Model 3共用平台,零部件复用率高达75%,研发成本骤降。尽管Model Y最初被用户吐槽是Model 3的放大版,但不置可否的是Model Y确实解决了用户对Model 3空间不足的槽点。\n\n  不过,最为关键的还是Model Y的价格足够低,直接降低了特斯拉的购买门槛,给那些对价格敏感,本犹豫要不要多花四五万的消费者一个充足的理由。\n\n  蔚来ET5T正在用一种经受了市场验证过的方式,直面与特斯拉的竞争。但同时,蔚来ET5T在智能化、空间表现、设计以及产品力上,都正在接近、超越特斯拉Model Y。\n\n  蔚来ET5T,平替特斯拉Model Y?\n\n  小家庭,预算30万左右,消费者到底会选哪款纯电动车?\n\n  全球市场的反馈是,特斯拉Model Y ——一款紧凑型SUV。2022年,Model Y的全球销量为74.7万辆,其在中国的销量为31.5万台,约占其全球份额的42.2%。\n\n  按照车型大小,SUV可以分为大型、中型、小型、紧凑型四大类。按照价位,SUV又可以分为实用型、经济型、中高档型、豪华型、超豪华型等。\n\n  Model Y 在中高端SUV的细分市场中一骑绝尘,可以说是没有对手。因为无论是奔驰EQC、宝马iX3,还是国产的比亚迪唐EV等,和Model Y相比,都不能对其构成威胁。奔驰EQC、宝马iX3这两款车型都是“油改电”,算不上真正的电动车。而比亚迪的智能化能力,远及不特斯拉,座舱、智驾上的核心模块还来自于供应商方案,并非自研。\n\n  雷峰网认为,此前,国内的自主品牌中只有蔚来的ES6能和Model Y一较高下。不过ES6的均价比Model Y高出一大截,二者入门版之间的价差大约在10万左右。但在蔚来推出ET5T后,局势必然会发生逆转。', 4, 5, 0, '2023-06-25 09:44:20', '2023-06-25 09:44:20', 0);

DROP TABLE IF EXISTS `news_type`;
CREATE TABLE `news_type` (
`tid` INT NOT NULL AUTO_INCREMENT COMMENT '新闻类型id',
`tname` VARCHAR(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '新闻类型描述',
`version` INT DEFAULT 1 COMMENT '乐观锁',
`is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除 0 未删除',
PRIMARY KEY (`tid`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;


INSERT INTO `news_type` (tid,tname) VALUES (1, '新闻');
INSERT INTO `news_type` (tid,tname) VALUES (2, '体育');
INSERT INTO `news_type` (tid,tname) VALUES (3, '娱乐');
INSERT INTO `news_type` (tid,tname) VALUES (4, '科技');
INSERT INTO `news_type` (tid,tname) VALUES (5, '其他');

DROP TABLE IF EXISTS `news_user`;
CREATE TABLE `news_user` (
`uid` INT NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户登录名',
`user_pwd` VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户登录密码密文',
`nick_name` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户昵称',
`version` INT DEFAULT 1 COMMENT '乐观锁',
`is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除 0 未删除',
PRIMARY KEY (`uid`) USING BTREE,
UNIQUE INDEX `username_unique`(`username`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;


INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (1, 'zhangsan', 'e10adc3949ba59abbe56e057f20f883e', '张三');
INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (2, 'lisi', 'e10adc3949ba59abbe56e057f20f883e', '李四');
INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (5, 'zhangxiaoming', 'e10adc3949ba59abbe56e057f20f883e', '张小明');
INSERT INTO `news_user` (uid,username,user_pwd,nick_name)VALUES (6, 'xiaohei', 'e10adc3949ba59abbe56e057f20f883e', '李小黑');

SET FOREIGN_KEY_CHECKS = 1;

前端代码

云盘自取

后端代码

导入依赖

  <parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>3.0.5</version>
</parent>
<groupId>com.jason</groupId>
<artifactId>springboot-heandline-part</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>

<!-- 数据库相关配置启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- druid启动器的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.18</version>
</dependency>

<!-- 驱动类-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>

appliation.yaml

# server配置
server:
port: 8080
servlet:
context-path: /

# 连接池配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:mysql:///sm_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

# mybatis-plus的配置
mybatis-plus:
type-aliases-package: com.atguigu.pojo
global-config:
db-config:
logic-delete-field: isDeleted #全局逻辑删除
id-type: auto #主键策略自增长
table-prefix: news_ # 设置表的前缀

druid兼容boot3文件

META-INF.spring

文件名:org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure

新建启动类

package com.jason;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@MapperScan("com.jason.mapper")
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class,args);
}

//配置mybatis-plus插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //分页
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); //乐观锁
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); //防全局修改和删除
return interceptor;
}

}

在idea中连接数据库 然后使用mybatisX的逆向工程

准备工具类

结果封装类

package com.jason.utils;
/**
* 全局统一返回结果类
*/
public class Result<T>{
// 返回码
private Integer code;
// 返回消息
private String message;
// 返回数据
private T data;
public Result(){}
// 返回数据
protected static <T> Result<T> build(T data) {
Result<T> result = new Result<T>();
if (data != null)
result.setData(data);
return result;
}
public static <T> Result<T> build(T body, Integer code, String message) {
Result<T> result = build(body);
result.setCode(code);
result.setMessage(message);
return result;
}
public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
Result<T> result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
/**
* 操作成功
* @param data baseCategory1List
* @param <T>
* @return
*/
public static<T> Result<T> ok(T data){
Result<T> result = build(data);
return build(data, ResultCodeEnum.SUCCESS);
}
public Result<T> message(String msg){
this.setMessage(msg);
return this;
}
public Result<T> code(Integer code){
this.setCode(code);
return this;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

统一返回结果状态信息类

package com.jason.utils;
/**
* 统一返回结果状态信息类
*
*/
public enum ResultCodeEnum {
SUCCESS(200,"success"),
USERNAME_ERROR(501,"usernameError"),
PASSWORD_ERROR(503,"passwordError"),
NOTLOGIN(504,"notLogin"),
USERNAME_USED(505,"userNameUsed");

private Integer code;
private String message;
private ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}

MD5加密

package com.jason.utils;
@Component
public class MD5Util {
public static String encrypt(String strSrc) {
try {
char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f'};
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
}