目录

[TOC]

MyBatis

官方文档

https://mybatis.net.cn/

实体类和建表语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
public class UserEntity implements Serializable {
/**
* id
*/
private Object id;

/**
* 用户名
*/
private String username;

/**
* 密码
*/
private String password;

/**
* 性别【0:男 1:女】
*/
private String gender;

private static final long serialVersionUID = 1L;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
public class Dept implements Serializable {
/**
* 部门id
*/
private Object deptId;

/**
* 部门名称
*/
private Integer deptName;

/**
* 指定部门下面的员工信息
*/
private List<Emp> emps;

private static final long serialVersionUID = 1L;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Data
public class Emp implements Serializable {
/**
* 员工id
*/
private Object empId;

/**
* 员工名称
*/
private String empName;

/**
* 年龄
*/
private Integer age;

/**
* 性别
*/
private String gender;

/**
* 部门id
*/
private Integer deptId;

/**
* 部门信息
*/
private Dept dept;

private static final long serialVersionUID = 1L;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TABLE `user` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户名',
`password` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码',
`gender` varchar(1) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '性别【0:男 1:女】',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';

CREATE TABLE `dept` (
`dept_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '部门id',
`dept_name` int NOT NULL COMMENT '部门名称',
PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='部门表'

CREATE TABLE `emp` (
`emp_id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '员工id',
`emp_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '员工名称',
`age` int DEFAULT NULL COMMENT '年龄',
`gender` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '性别',
`dept_id` int DEFAULT NULL COMMENT '部门id',
PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='员工表'

mybatis-hello

创建数据库

1
2
3
4
5
6
7
CREATE TABLE `user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id',
`username` VARCHAR ( 255 ) NULL COMMENT '用户名',
`password` VARCHAR ( 255 ) NULL COMMENT '密码',
`gender` VARCHAR ( 1 ) NULL COMMENT '性别【0:男 1:女】',
PRIMARY KEY ( `id` )
) COMMENT='用户信息';

创建maven项目且导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--log4j日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>

创建mybatis核心配置文件

习惯上命名为mybatis-config.xml。 核心配置文件主要用于配置连接数据库的环境以MyBatis的全局配置信息核心配置文件存放的位置是src/main/resources目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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 default="development">
<environment id="development">
<!--事务管理器-->
<transactionManager type="JDBC"/>
<!--连接数据库的配置信息-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm-study?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<!--引入映射文件【数据库和实体类】-->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>

</configuration>

新建mapper接口和mapper配置文件

放在src/main/java下面

1
2
3
4
5
6
7
public interface UserMapper {
/**
* 添加用户信息
* @return 修成成功的条数
*/
int insert();
}

命名规则为实体类接口名称+Mapper.xml,例如上面为UserMapper,则XML文件为UserMapper.xml

注意:还需要在mybatis的核心配置文件mybatis-config.xml中的mappers标签中将该XML指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:指定Mapper接口所在位置的全路径-->
<mapper namespace="com.xiaofei.mybatis.mapper.UserMapper">

<!--
mapper接口和映射文件要保证两个一致
mapper接口的全类名和映射文件的namespace一致
mapper接口中的方法的方法名要和映射文件中的sqL的d保持一致
-->
<!--添加用户信息-->
<insert id="insert">
insert into user(username, password, gender) value(1,1,1)
</insert>

</mapper>

编写sqlSession工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class SqlSessionUtil {

public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
try {
//读取MyBatis的核心配置文件的输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder
= new SqlSessionFactoryBuilder();

//通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory
= sqlSessionFactoryBuilder.build(inputStream);

//创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
// sqlSession = sqlSessionFactory.openSession();

//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
sqlSession = sqlSessionFactory.openSession(true);

} catch (IOException e) {
e.printStackTrace();
}
return sqlSession;
}
}

编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import com.xiaofei.mybatis.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;

public class UserTest {

@Test
public void test() {
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

//调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,通过调用的方法名匹配 映射文件中的SQL标签,并执行标签中的SQL语句
int result = userMapper.insert();
System.out.println("结果:"+result);

//如果使用的是sqlSessionFactory.openSession();创建对象则需要手动提交事务
//sqlSession.commit();

//关闭SqlSession
sqlSession.close();
}
}

添加日志框架Log4j

log4j配置文件名为log4j.xml存放位置是src/main/resources目录下面

日志级别:fatal(致命)> error(错误)> warn(警告)> info(信息)> debug(调试)

从左到右打印的内容越来越详细【debug日志信息最全】

  • 引入依赖

    1
    2
    3
    4
    5
    6
    <!--log4j日志-->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
  • 添加log4j配置文件,名字为log4j.xml,放在resources下面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
    <param name="Encoding" value="UTF-8"/>
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}%m (%F:%L) \n"/>
    </layout>
    </appender>

    <logger name="java.sql">
    <level value="debug"/>
    </logger>

    <logger name="org.apache.ibatis">
    <level value="info"/>
    </logger>

    <root>
    <level value="debug"/>
    <appender-ref ref="STDOUT"/>
    </root>

    </log4j:configuration>

mybatis配置文件详解

配置文件中的标签必须按照固定的顺序

官方文档地址:https://mybatis.net.cn/configuration.html

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?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>

<!--
MyBatis核心配置文件中的标签必须要按照指定的顺序配置:
configuration(配置)properties(属性)settings(设置)typeAliases(类型别名)
typeHandlers(类型处理器)objectFactory(对象工厂)plugins(插件)environments(环境配置)
environment(环境变量)transactionManager(事务管理器)
dataSource(数据源)databaseIdProvider(数据库厂商标识)mappers(映射器)
-->

<!--引入properties文件,此后就可以在当前文件中使用${key}的方式访问value-->
<properties resource="jdbc.properties" />

<!--
typeAliases:设置类型别名,即为某个具体的类型设置一个别名
在MyBatis的范围中,就可以使用别名表示一个具体的类型
-->
<typeAliases>
<!-- type:设置需要起别名的类型 alias:设置某个类型的别名 -->
<!--<typeAlias type="com.xiaofei.mybatis.entity.User" alias="abc"/>-->

<!--若不设置alias,当前的类型拥有默认的别名,即类名且不区分大小写-->
<!--<typeAlias type="com.xiaofei.mybatis.entity.User"/>-->

<!--通过包设置类型别名,指定包下所有的类型将全部拥有默认的别名,别名即类名且不区分大小写-->
<!--设置前:<select id="select" resultType="com.xiaofei.mybatis.entity.UserEntity">-->
<!--设置后:<select id="select" resultType="UserEntity">-->
<package name="com.xiaofei.mybatis.entity"/>
</typeAliases>

<!--
environments:配置连接数据库的环境
属性:
default:设置默认使用的环境的id
比如:
development:使用id为development的environment的配置
prod:使用id为prod的environment的配置
-->
<environments default="development">
<!--
environment:设置一个具体的连接数据库的环境
属性:
id:设置环境的唯一标识,不能重复
-->
<environment id="development">
<!--
transactionManager:设置事务管理器
属性:
type:设置事务管理的方式
type="JDBC|MANAGED"
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--
dataSource:设置数据源
属性:
type:设置数据源的类型
type="POOLED|UNPOOLED|JNDI"
POOLED:表示使用数据库连接池
UNPOOLED:表示不使用数据库连接池
JNDI:表示使用上下文中的数据源
-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>

<environment id="prod">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>

<!--引入mybatis的映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
以包的方式引入映射文件,但是必须满足两个条件:
1、mapper接口和映射文件所在的包必须一致
2、mapper接口的名字和映射文件的名字必须一致
-->
<package name="com.xiaofei.mybatis.mapper"/>
</mappers>
</configuration>

mybatis查询返回

查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射 关系

  • resultType:自动映射,用于属性名和表中字段名一致的情况
  • resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
1
2
3
4
5
6
7
8
<select id="resultType" resultType="">
SELECT *FROM user
</select>

<!--使用resultMap还需要再使用resultMap标签一一映射 后续再补充-->
<select id="resultMap" resultMap="">
SELECT *FROM user
</select>

mybatis参数获取

  • #{}(推荐使用):占位符赋值,若为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号
  • ${}(不推荐使用):字符串拼接,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引

如果有多个参数,则需要使用@Param参数来标识参数

1
int insert(@Param("username") String username, @Param("password") String password);
1
2
3
<insert id="insert">
INSERT INTO `user`(username, password) value (#{username}, #{password})
</insert>

mybatis各种查询

获取sqlSession工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class SqlSessionUtil {

public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
try {
//获取核心配置文件的输入流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSession;
}
}

查询一个实体类对象

1
UserEntity selectById(@Param("id") Long id);
1
2
3
4
<!--根据id查询-->
<select id="selectById" resultType="com.xiaofei.mybatis.entity.UserEntity">
SELECT *FROM user WHERE id = #{id}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void selectById() {
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
UserEntityMapper userMapper = sqlSession.getMapper(UserEntityMapper.class);

System.out.println(userMapper.selectById(1L));

sqlSession.close();
}

查询一个list集合

1
List<UserEntity> selectList();
1
2
3
4
<!--查询集合-->
<select id="selectList" resultType="com.xiaofei.mybatis.entity.UserEntity">
SELECT *FROM user
</select>
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void selectList() {
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
UserEntityMapper userMapper = sqlSession.getMapper(UserEntityMapper.class);

userMapper.selectList().forEach(System.out::println);

sqlSession.close();
}

查询单个数据

mybatis中给Java类名已经设置了别名,具体使用查看链接:https://mybatis.net.cn/configuration.html#typeAliases

1
int count();
1
2
3
4
<!--查询总数-->
<select id="count" resultType="java.lang.Integer">
SELECT COUNT(*) FROM user
</select>
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void count(){
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
UserEntityMapper userMapper = sqlSession.getMapper(UserEntityMapper.class);

System.out.println(userMapper.count());

sqlSession.close();
}

查询返回Map

查询一条数据为map对象

1
Map<String, Object> selectMap(@Param("id") Long id);
1
2
3
4
<!--查询返回Map-->
<select id="selectMap" resultType="java.util.Map">
SELECT *FROM user WHERE id = #{id}
</select>
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void selectMap() {
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
UserEntityMapper userMapper = sqlSession.getMapper(UserEntityMapper.class);

System.out.println(userMapper.selectMap(1L));

sqlSession.close();
}

查询多条数据为map集合

1
List<Map<String, Object>> selectMapList();
1
2
3
<select id="selectMapList" resultType="java.util.Map">
SELECT *FROM user
</select>
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void selectMapList() {
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
UserEntityMapper userMapper = sqlSession.getMapper(UserEntityMapper.class);

userMapper.selectMapList().forEach(System.out::println);

sqlSession.close();
}

使用MapKey注解(不推荐使用)

@MapKey:将查询的某个字段的值作为大的map的键

  • 查询一条数据

    1
    2
    @MapKey("id")
    Map<String, Object> selectMap(@Param("id") Long id);
  • 查询多条数据

    1
    2
    @MapKey("id")
    Map<String, Object> selectMapList();

特殊SQL执行

模糊查询

1
List<UserEntity> getByUsername(@Param("username") String username);
1
2
3
4
5
6
7
8
9
10
11
<!--根据用户名模糊查询用户信息-->
<select id="getByUsername" resultType="com.xiaofei.mybatis.entity.UserEntity">
<!-- 方式一:使用${} -->
<!-- SELECT *FROM `user` WHERE `username` LIKE '%${username}%' -->

<!-- 方式二:CONCAT函数 (推荐使用)-->
<!-- SELECT *FROM `user` WHERE `username` LIKE CONCAT('%',#{username},'%') -->

<!-- 方式三:使用 "" 将 % 包含(推荐使用) -->
SELECT *FROM `user` WHERE `username` LIKE "%"#{username}"%"
</select>

批量删除

推荐使用可以查看动态SQL来实现批量删除

1
int batchDelete(@Param("ids") String ids);
1
2
3
4
<delete id="batchDelete">
<!-- 这里不能使用 #{} 来取值 ,使用 #{}会在两边加上 '' 如果id是int/long类型会报错 建议使用动态SQL实现批量删除 -->
DELETE FROM `user` WHERE id IN (${ids})
</delete>

动态设置表名

1
List<UserEntity> getByUsername(@Param("tableName") String tableName);
1
2
3
4
<select id="getByUsername" resultType="com.xiaofei.mybatis.entity.UserEntity">
<!-- 不能使用 #{} ,#{} 会为参数自动加上 '' ,表名中加 '' 会报错 ,只能使用${} -->
SELECT *FROM ${tableName}
</select>

添加功能获取自增的主键

在JDBC中就已经可以实现新增获取自增主键,mybatis是封装的JDBC

1
void insertUser(UserEntity user);
1
2
3
4
5
6
7
8
9
<!--
useGeneratedKeys:表示当前添加功能使用自增的主键
keyProperty:将添加的数据的自增主键为实体类类型的参数的属性赋值
例如:id为自增,则keyProperty值设置为id
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id" parameterType="com.xiaofei.mybatis.entity.UserEntity">
INSERT INTO `user` (username, password, gender)
VALUES (#{username}, #{password}, #{gender})
</insert>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void insertUser() {
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
UserEntityMapper userMapper = sqlSession.getMapper(UserEntityMapper.class);

UserEntity userEntity = new UserEntity();
userEntity.setUsername("1");
userEntity.setGender("2");
userEntity.setPassword("3");

userMapper.insertUser(userEntity);

//输出id
System.out.println(userEntity.getId());

sqlSession.close();
}

自定义映射resultMap

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射

数据库字段和类属性处理

起别名

1
Emp getById(@Param("id") Integer id);
1
2
3
4
5
<select id="getById" resultType="com.xiaofei.mybatis.entity.Emp">
SELECT emp_id empId, emp_name empName, age, gender
FROM emp
WHERE emp_id = #{id}
</select>

全局配置字段匹配规则为驼峰

修改mybatis核心配置文件mybatis-config.xml中的setting

setting标签具体配置:https://mybatis.net.cn/configuration.html#settings

1
2
3
4
<settings>
<!-- 开启mybatis驼峰命名映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
1
2
3
4
<!--根据id获取-->
<select id="getById" resultType="com.xiaofei.mybatis.entity.Emp">
SELECT *FROM emp WHERE emp_id = #{id}
</select>
1
Emp getById(@Param("id") Integer id);

使用resultMap自定义映射

1
Emp getById(@Param("id") Integer id);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--
resultMap:设置自定义的映射关系
id:唯一标识
type:处理映射关系的实体类的类型
常用的标签:
id:处理主键和实体类中属性的映射关系
result:处理普通字段和实体类中属性的映射关系
association:处理多对一/一对一的映射关系(处理实体类类型的属性)
collection:处理一对多的映射关系(处理集合类型的属性)
标签中常用属性:
column:设置映射关系中的字段名,必须是sql查询出的某个字段
property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
jdbcType:对应Java中的哪个类型,可以不用指定
-->
<resultMap id="BaseResultMap" type="com.xiaofei.mybatis.entity.Emp">
<id property="empId" column="emp_id" jdbcType="OTHER"/>
<result property="empName" column="emp_name" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="gender" column="gender" jdbcType="VARCHAR"/>
</resultMap>

<!-- 使用resultMap自定义映射 -->
<select id="getById" resultMap="BaseResultMap">
SELECT *FROM emp WHERE emp_id = #{id}
</select>

多对一/一对一映射处理

使用级联方式处理映射关系

1
Emp selectById(@Param("empId") Integer empId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="BaseResultMap" type="com.xiaofei.mybatis.entity.Emp">
<id property="empId" column="emp_id" jdbcType="OTHER"/>
<result property="empName" column="emp_name" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="gender" column="gender" jdbcType="VARCHAR"/>
<result property="deptId" column="dept_id" jdbcType="INTEGER"/>
<result property="dept.deptId" column="dept_id"/>
<result property="dept.deptName" column="dept_name"/>
</resultMap>

<!--根据id查询-->
<select id="selectById" resultMap="BaseResultMap">
SELECT * FROM emp LEFT JOIN dept ON emp.dept_id = dept.dept_id WHERE emp.emp_id = #{empId}
</select>

使用association处理映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 多对一/一对一映射 -->
<resultMap id="AssociationResultMap" type="com.xiaofei.mybatis.entity.Emp">
<id property="empId" column="emp_id" jdbcType="INTEGER"/>
<result property="empName" column="emp_name" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="gender" column="gender" jdbcType="VARCHAR"/>
<result property="deptId" column="dept_id" jdbcType="INTEGER"/>
<!-- 使用association处理多对一/一对一映射 -->
<association property="dept" column="dept">
<id property="deptId" column="dept_id" jdbcType="INTEGER"/>
<result property="deptName" column="dept_name" jdbcType="VARCHAR"/>
</association>
</resultMap>

<!--根据id查询-->
<select id="selectById" resultMap="BaseResultMap">
SELECT * FROM emp LEFT JOIN dept ON emp.dept_id = dept.dept_id WHERE emp.emp_id = #{empId}
</select>

使用association进行分步查询

①dept

1
Dept selectById(@Param("deptId") String deptId);
1
2
3
<select id="selectById" resultType="com.xiaofei.mybatis.entity.Dept">
SELECT *FROM dept WHERE dept_id = #{deptId}
</select>

②emp

1
Emp selectByStep(@Param("empId") Integer empId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--分步查询映射-->
<resultMap id="AssociationByStep" type="com.xiaofei.mybatis.entity.Emp">
<id property="empId" column="emp_id" jdbcType="INTEGER"/>
<result property="empName" column="emp_name" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="gender" column="gender" jdbcType="VARCHAR"/>
<result property="deptId" column="dept_id" jdbcType="INTEGER"/>
<!--
property:指定哪个字段要进行分步查询
select:下一步指定哪一个类的哪一个方法进行查询,下一步要执行的方法
column:将SQL查询的结果中的某一个值作为select指定的方法需要的参数【不是对应实体类的参数,是SQL对应参数的名字】
-->
<association property="dept" select="com.xiaofei.mybatis.mapper.DeptMapper.selectById" column="dept_id"/>
</resultMap>
<select id="selectByStep" resultMap="AssociationByStep">
SELECT *FROM emp WHERE emp_id = #{empId}
</select>

一对多映射处理

使用collection处理映射关系

1
Dept selectInfoById(@Param("deptId") Integer deptId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--一对多-->
<resultMap id="CollectionResultMap" type="com.xiaofei.mybatis.entity.Dept">
<id property="deptId" column="dept_id" jdbcType="OTHER"/>
<result property="deptName" column="dept_name" jdbcType="VARCHAR"/>
<!--
property:指定实体类中一对多的参数
ofType:一对多的实体类的类型,一般指向另外一个实体类
-->
<collection property="emps" ofType="com.xiaofei.mybatis.entity.Emp">
<id property="empId" column="emp_id" jdbcType="INTEGER"/>
<result property="empName" column="emp_name" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="gender" column="gender" jdbcType="VARCHAR"/>
<result property="deptId" column="dept_id" jdbcType="INTEGER"/>
</collection>
</resultMap>

<!--查询指定部门下面的部门信息和该部门下面的员工信息-->
<select id="selectInfoById" resultMap="CollectionResultMap">
SELECT *
FROM dept
LEFT JOIN emp ON dept.dept_id = emp.dept_id
WHERE dept.dept_id = #{deptId}
</select>

使用collection进行分步查询

①emp

1
List<Emp> selectByDeptId(@Param("deptId") Integer deptId);
1
2
3
4
5
<select id="selectByDeptId" resultType="com.xiaofei.mybatis.entity.Emp">
SELECT *
FROM emp
WHERE dept_id = #{deptId}
</select>

②dept

懒加载具体使用查看分步查询优点

1
Dept selectInfoById(@Param("deptId") Integer deptId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--分步查询一对多-->
<resultMap id="CollectionResultMapStep" type="com.xiaofei.mybatis.entity.Dept">
<id property="deptId" column="dept_id" jdbcType="OTHER"/>
<result property="deptName" column="dept_name" jdbcType="VARCHAR"/>
<!--
property:指定哪个字段要进行分步查询
fetchType="lazy(延迟加载)|eager(立即加载),需要先在全局配置文件中进行配置
select:下一步指定哪一个类的哪一个方法进行查询,下一步要执行的方法
column:将SQL查询的结果中的某一个值作为select指定的方法需要的参数【不是对应实体类的参数,是SQL对应参数的名字】
-->
<collection property="emps" fetchType="eager" select="com.xiaofei.mybatis.mapper.EmpMapper.selectByDeptId"
column="dept_id"/>
</resultMap>

<!--查询指定部门下面的部门信息和该部门下面的员工信息-->
<select id="selectInfoById" resultMap="CollectionResultMapStep">
SELECT *
FROM dept
WHERE dept.dept_id = #{deptId}
</select>

分步查询优点

分步查询的优点:可以实现延迟加载

但是必须在核心配置文件中设置全局配置信息:

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载

此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载,

fetchType=”lazy(延迟加载)|eager(立即加载)”

  • 全局开启延迟加载,在mybatis-config.xml文件的setting里面配置

    1
    2
    3
    4
    5
    <!--lazyLoadingEnabled和aggressiveLazyLoading需要一起配置-->
    <!--开启延迟加载-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--按需加载-->
    <setting name="aggressiveLazyLoading" value="false"/>

动态SQL

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。

if

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行,搭配where标签一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<select id="selectList" resultType="com.xiaofei.mybatis.entity.Emp">
SELECT *FROM emp
<where>
<if test="empId != null and empId != ''">
AND `emp_id` = #{empId}
</if>
<if test="empName != null and empName != ''">
AND `emp_name` = #{empName}
</if>
<if test="age != null and age != ''">
AND `age` = #{age}
</if>
<if test="gender != null and gender != ''">
AND `gender` = #{gender}
</if>
<if test="deptId != null and deptId != ''">
AND `dept_id` = #{deptId}
</if>
</where>
</select>

where

where和if一般结合使用:

若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字

若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉

注意:where标签不能去掉条件最后多余的and

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<select id="selectList" resultType="com.xiaofei.mybatis.entity.Emp">
SELECT *FROM emp
<where>
<if test="empId != null and empId != ''">
AND `emp_id` = #{empId}
</if>
<if test="empName != null and empName != ''">
AND `emp_name` = #{empName}
</if>
<if test="age != null and age != ''">
AND `age` = #{age}
</if>
<if test="gender != null and gender != ''">
AND `gender` = #{gender}
</if>
<if test="deptId != null and deptId != ''">
AND `dept_id` = #{deptId}
</if>
</where>
</select>

trim

  • trim用于去掉或添加标签中的内容
  • 常用属性:
    • prefix:在trim标签中的内容的前面添加某些内容
    • prefixOverrides:在trim标签中的内容的前面去掉某些内容
    • suffix:在trim标签中的内容的后面添加某些内容
    • suffixOverrides:在trim标签中的内容的后面去掉某些内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<insert id="insert">
INSERT INTO emp
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="empId != null and empId != ''">
`emp_id`,
</if>
<if test="empName != null and empName != ''">
`emp_name`,
</if>
<if test="age != null and age != ''">
`age`,
</if>
<if test="gender != null and gender != ''">
`gender`,
</if>
<if test="deptId != null and deptId != ''">
`dept_id`,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="empId != null and empId != ''">
#{empId},
</if>
<if test="empName != null and empName != ''">
#{empName},
</if>
<if test="age != null and age != ''">
#{age},
</if>
<if test="gender != null and gender != ''">
#{gender},
</if>
<if test="deptId != null and deptId != ''">
#{deptId},
</if>
</trim>
</insert>

choose、when、otherwise

choose、when、otherwise相当于if…else。当有一个when标签满足后,后面的when标签将不会再进行比较

1
Dept chooseUse(Dept dept);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- when至少设置一个,otherwise最多设置一个 -->
<select id="chooseUse" resultType="com.xiaofei.mybatis.entity.Dept">
SELECT *
FROM dept
<where>
<choose>
<when test="deptId != null and deptId != ''">
AND `dept_id` = #{deptId}
</when>
<when test="deptName != null and deptName != ''">
AND `dept_name` = #{deptName}
</when>
</choose>
</where>
</select>

foreach

  • collection:设置要循环的数组或集合
  • item:用一个字符串表示数组或集合中的每一个数据
  • separator:设置每次循环的数据之间的分隔符
  • open:循环的所有内容以什么开始
  • close:循环的所有内容以什么结束
1
2
3
4
5
6
7
8
9
/**
* 批量添加
*/
int insertBatch(@Param("depts") List<Dept> depts);

/**
* 批量删除
*/
int deleteBatch(@Param("ids") List<Integer> ids);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--批量添加-->
<insert id="insertBatch">
INSERT INTO dept ( dept_name ) VALUES
<foreach collection="depts" item="dept" separator=",">
(#{dept.deptName})
</foreach>
</insert>

<!--批量删除-->
<delete id="deleteBatch">
DELETE FROM dept WHERE dept_id IN
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>

动态SQL

可以记录一段sql,在需要用的地方使用include标签进行引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- 查询字典公共SQL -->
<sql id="Base_Column_List">
`emp_id`, `emp_name`, `age`, `gender`, `dept_id`
</sql>

<!-- 查询判断公共SQL -->
<sql id="Base_Select_If">
<where>
<if test="empId != null and empId != ''">
AND `emp_id` = #{empId}
</if>
<if test="empName != null and empName != ''">
AND `emp_name` = #{empName}
</if>
<if test="age != null and age != ''">
AND `age` = #{age}
</if>
<if test="gender != null and gender != ''">
AND `gender` = #{gender}
</if>
<if test="deptId != null and deptId != ''">
AND `dept_id` = #{deptId}
</if>
</where>
</sql>

<!-- 根据条件分页查询基础信息-员工表列表 -->
<select id="selectList" resultType="Emp">
SELECT
<include refid="Base_Column_List"/>
FROM emp
<include refid="Base_Select_If"/>
</select>

mybatis缓存

mybatis一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查

询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

使一级缓存失效的四种情况:

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存

mybatis一级缓存默认开启的,不需要手动设置

image-20230205165217279

mybatis二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

  • 二级缓存开启的条件:

    • 在核心配置文件mybatis-config.xmlsetting标签里面设置全局配置属性cacheEnabled=”true”,默认为true,不需要设置

    • XxxMapper.xml映射文件中设置<cache/>标签

    • 二级缓存必须在SqlSession关闭或提交之后有效

    • 查询的数据所转换的实体类类型必须实现序列化的接口Serializable

  • 使二级缓存失效的情况:

    • 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

二级缓存的相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:

  • eviction属性:缓存回收策略,默认的是LRU。

    • LRU (Least Recently Used)–最近最少使用的:移除最长时间不被使用的对象。

    • FIFO(First in First out)–先进先出:按对象进入缓存的顺序来移除它们。

    • SOFT–软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK–弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
  • flushInterval属性:刷新间隔,单位毫秒。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

  • size属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly属性:只读,true/false

    • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。

    • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。

mybatis缓存查询顺序

先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。

如果二级缓存没有命中,再查询一级缓存

如果一级缓存也没有命中,则查询数据库

SqlSession关闭之后,一级缓存中的数据会写入二级缓存

整个第三方缓存EHcache(很少使用)

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!--ehcache-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

各jar包功能

jar包名称 作用
mybatis-ehcache MyBatis和EHCache的整合包
ehcache EHCache核心包
slf4j-api SLF4J日志门面包
logback-classic 支持SLF4J门面接口的一个具体实现

创建EHCache的配置文件ehcache.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="C:\Users\19030\Desktop\tmp\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

设置二级缓存类型

1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。创建logback的配置文件logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是: 时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>

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

<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.xiaofei.mybatis.mapper" level="DEBUG"/>
</configuration>

EHCache配置文件说明

image-20230205171715294

mybatis逆向工程

克隆下面项目地址,进行代码生成

Gitee:https://gitee.com/xiao-i-fei/xiaofei-generator

GitHub:https://github.com/xiao-i-fei/xiaofei-generator

mybatis-pagehelper分页插件使用

官方文档:https://pagehelper.github.io/

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>

使用

  • 在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
    • pageNum:当前页的页码
    • pageSize:每页显示的条数
  • 在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)获取分页相关数据
    • list:分页之后的数据
    • navigatePages(可选参数):导航分页的页码数,一般为pageNum
1
2
3
<select id="list" resultType="Dept">
SELECT *FROM dept
</select>
1
List<Dept> list();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void chooseUse() {
//创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交,否则需要sqlSession.commit();提交事务
SqlSession sqlSession = SqlSessionUtil.getSqlSession();

//通过代理模式创建UserMapper接口的代理实现类对象
DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);

//使用分页插件
PageHelper.startPage(1,2);

//查询数据
List<Dept> depts = deptMapper.list();

//获取分页信息
PageInfo<Dept> pageInfo = new PageInfo<Dept>(depts, 1);

depts.forEach(System.out::println);

System.out.println(pageInfo);

sqlSession.close();
}

参数解释

参数 意义
pageNum 当前页的页码
pageSize 每页显示的条数
size 当前页显示的真实条数
total 总记录数
pages 总页数
prePage 上一页的页码
nextPage 下一页的页码
isFirstPage/isLastPage 是否为第一页/最后一页
hasPreviousPage/hasNextPage: 是否存在上一页/下一页
navigatePages 导航分页的页码数
navigatepageNums 导航分页的页码,[1,2,3,4,5]

Spring

Spring Framework

Spring Framework特性

Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework 为基础的。

  • 非侵入式:使用Spring Framework开发应用程序时,Spring对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于Spring Framework开发应用程序时结构清晰、简洁优雅。
  • 控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。
  • 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。
  • 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。
  • 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。
  • 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
  • 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在Spring Framework的基础上全部使用Spring来实现。

Spring Framework五大功能模块

功能模块 功能介绍
Core Container 核心容器,在Spring环境下使用任何功能都是必须基于IOC容器
AOP&Aspects 面向切面编程
Testing 提供了对junit或TestNG测试框架的整合
Data Access / Integration 提供了对数据访问 / 集成的功能
Spring MVC 提供了面向Web应用程序的集成功能

IOC

IOC容器

IOC思想

IOC:Inversion of Control,翻译过来是反转控制

  • 获取资源的传统方式
    • 自己做饭:买菜、洗菜、择菜、改刀、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握。
    • 在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效
      率。
  • 反转控制方式获取资源
    • 点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。
    • 反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源
      的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
  • DI
    • DI:Dependency Injection,翻译过来是依赖注入。
    • DI是IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
    • 所以结论是:IOC就是一种反转控制的思想,而DI是对IOC的一种具体实现

IOC容器在Spring中的实现

image-20230211142922073

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器 对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容 器对象
ConfigurableApplicationContext ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让ApplicationContext 具有启动、关闭和刷新上下文的能力
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对 象,并将对象引入存入 ServletContext 域中

基于XML管理bean

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<!--spring-context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>

创建实体类

1
2
3
4
5
6
7
8
9
@Data
@NoArgsConstructor
@AllArgsConstructor
public class InternalBean {
/**
* 部门名称
*/
private String deptName;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SpringBean {
/**
* id编号
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 性别
*/
private String gender;
/**
* 特殊值处理1
*/
private String specialValue1;
/**
* 特殊值处理2
*/
private String specialValue2;
/**
* 特殊值处理3
*/
private String specialValue3;
/**
* 兴趣爱好
*/
private String[] hobby;
/**
* 别名
*/
private List<String> otherNames;
/**
* Map类型参数
*/
private Map<String, String> mapValue;
/**
* 引入其他类
*/
private InternalBean internalBean;
/**
* 引入其他类
*/
private List<InternalBean> internalBeans;
}

创建配置文件

文件名字为:ApplicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--setter注入-->
<bean id="springBean" class="com.xiaofei.spring.SpringBean">
<!--
property标签:通过组件类的setXxx()方法给组件对象设置属性
name属性:指定属性名(这个属性名是getXxx()、setXxx()方法定义的,和成员变量无关)
value属性:指定属性值
-->

<!--普通值-->
<property name="id" value="12"/>

<!--普通值-->
<property name="username" value="xiaofei"/>

<!--特殊值:null值-->
<property name="specialValue1">
<null/>
</property>

<!--特殊值:比较符号,使用xml实体来代替-->
<property name="specialValue2" value="a &lt; b"/>

<!--特殊值:使用CDATA节,<![CDATA[值]]>-->
<property name="specialValue3">
<value><![CDATA[《特殊值3》]]>--></value>
</property>

<!--数组参数赋值-->
<property name="hobby">
<array>
<value>吃饭</value>
<value>睡觉</value>
</array>
</property>

<!--List集合-->
<property name="otherNames">
<list>
<value>xiaofei1</value>
<value>xiaofei2</value>
</list>
</property>

<!--Map-->
<property name="mapValue">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
<entry key="key3" value="value3"/>
</map>
</property>

<!--类类型参数注入,该方法只是其中一种注入方式,也可以在外部定义一个新的bean,然后使用ref注入其他外部bean-->
<!-- <property name="internalBean" ref="internalBean1"/> -->
<property name="internalBean">
<!--内部类bean注入可以不需要设置id属性-->
<bean id="inner" class="com.xiaofei.spring.InternalBean">
<property name="deptName" value="特殊部门"/>
</bean>
</property>

<!--其他类类型集合bean-->
<property name="internalBeans">
<list>
<ref bean="internalBean1"/>
<ref bean="internalBean2"/>
<ref bean="internalBean3"/>
</list>
</property>
</bean>

<!--普通参数注入-->
<bean id="internalBean1" class="com.xiaofei.spring.InternalBean">
<property name="deptName" value="开发部门"/>
</bean>

<!--
p命名空间注入,在beans标签中加入xmlns:p="http://www.springframework.org/schema/p"
-->
<bean id="internalBean2" class="com.xiaofei.spring.InternalBean" p:deptName="研发部门"/>

<!--构造器参数注入-->
<bean id="internalBean3" class="com.xiaofei.spring.InternalBean">
<!--按照顺序设置值,可以不需要指定name-->
<constructor-arg name="deptName" value="销售部门"/>
</bean>

</beans>
1
2
3
4
5
6
7
8
@Test
public void test1() {
//获取IOC容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//获取IOC容器中的bean
SpringBean springBean = (SpringBean) ioc.getBean("springBean");
System.out.println(springBean);
}

引入外部配置文件

properties文件
1
external.deptName=开发部门
spring配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 引入外部属性properties文件 -->
<context:property-placeholder location="classpath:external.properties"/>

<!--添加bean-->
<bean id="internalBean" class="com.xiaofei.spring.InternalBean">
<!--使用${}引入外部配置文件的值-->
<property name="deptName" value="${external.deptName}"/>
</bean>
</beans>
测试
1
2
3
4
5
6
7
8
@Test
public void test2() {
//获取IOC容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("ExternalContext.xml");
//获取IOC容器中的bean
InternalBean internalBean = (InternalBean) ioc.getBean("internalBean");
System.out.println(internalBean);
}

Bean作用域

在bean标签的scope属性指定

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表

取值 含义 创建对象时机
singleton(默认) 在IOC容器中,这个bean的对象始终为单实例 IOC容器初始化时
propotype 这个bean在IOC容器中有多个实例 获取bean时

如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用)

取值 含义
request 在一个请求范围内有效
session 在一个会话范围内有效

bean的生命周期

  1. bean对象创建(调用无参构造器)
  2. 给bean对象设置属性
  3. bean对象初始化之前操作(由bean的后置处理器负责)
  4. bean对象初始化(需在配置bean时指定初始化方法)
  5. bean对象初始化之后操作(由bean的后置处理器负责)
  6. bean对象就绪可以使用
  7. bean对象销毁(需在配置bean时指定销毁方法)
  8. IOC容器关闭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Data
@AllArgsConstructor
public class InternalBean {
/**
* 部门名称
*/
private String deptName;

public InternalBean() {
System.out.println("1、创建对象");
}

public void setDeptName(String deptName){
System.out.println("2、依赖注入");
this.deptName = deptName;
}

public void initMethod(){
System.out.println("3、初始化");
}

public void destroyMethod(){
System.out.println("5、销毁");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">



<!--
使用init-method属性指定初始化方法
使用destroy-method属性指定销毁方法
-->
<bean id="internalLifeCycle" class="com.xiaofei.spring.InternalBean" scope="singleton" init-method="initMethod"
destroy-method="destroyMethod">
<property name="deptName" value="测试部门"/>
</bean>

</beans>
1
2
3
4
5
6
7
8
9
@Test
public void test1() {
//获取IOC容器
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//获取IOC容器中的bean
InternalBean internalBean = (InternalBean) ioc.getBean("internalLifeCycle");
System.out.println("4、通过IOC容器获取bean并使用");
ioc.close();
}

FactoryBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。

整合Mybatis时,Spring就是通过FactoryBean机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface FactoryBean<T> {

String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

@Nullable
T getObject() throws Exception;

@Nullable
Class<?> getObjectType();

default boolean isSingleton() {
return true;
}

}
  1. 创建UserEntity

    1
    2
    3
    @Data
    public class UserEntity {
    }
  2. 创建UserFactoryBean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class UserFactory implements FactoryBean<UserEntity> {
    @Override
    public UserEntity getObject() throws Exception {
    return new UserEntity();
    }

    @Override
    public Class<?> getObjectType() {
    return UserEntity.class;
    }
    }
  3. 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 将自己创建好的工厂Bean注入到添加到Spring中 -->
    <bean class="com.xiaofei.spring.factory.UserFactory"/>

    </beans>
  4. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void test3() {
    //获取IOC容器
    ApplicationContext ioc = new ClassPathXmlApplicationContext("SpringFactory.xml");
    //获取IOC容器中的bean
    UserEntity userEntity = ioc.getBean(UserEntity.class);
    System.out.println(userEntity);
    }

基于注解管理Bean

注解

  • @Component:将类标识为普通组件
  • @Controller:将类标识为控制层组件
  • @Service:将类标识为业务层组件
  • @Repository:将类标识为持久层组件

组件扫描

  • 最基本的扫描

    1
    <context:component-scan base-package="com.xiaofei"/>
  • 指定要排除的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <context:component-scan base-package="com">
    <!-- context:exclude-filter标签:指定排除规则 -->
    <!--
    type:设置排除或包含的依据
    type="annotation",排除某个注解标注的类,expression中设置要排除的注解的全类名
    type="assignable",排除某个类,expression中设置要排除的类型的全类名
    -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>

    <context:exclude-filter type="assignable" expression="com.xiaofei.spring.bean.AutoWireBean"/>
    </context:component-scan>
  • 仅扫描指定组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <context:component-scan base-package="com.xiaofei.spring">
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <!-- 此时必须设置use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
    <!--
    type:设置排除或包含的依据
    type="annotation",根据注解排除,expression中设置要排除的注解的全类名
    type="assignable",根据类型排除,expression中设置要排除的类型的全类名
    -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>

    <context:include-filter type="assignable" expression="com.xiaofei.spring.bean.AutoWireBean"/>
    </context:component-scan>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void test3() {
    //获取IOC容器
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowire.xml");

    //获取IOC容器中的bean
    AutoWireBean autoWireBean = ioc.getBean(AutoWireBean.class);

    System.out.println(autoWireBean);
    }
  • 组件对应的id:在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用 注解后,每个组件仍然应该有一个唯一标识。

    • 默认情况:组件名称首字母小写,例如UserService是userService
    • 通过value属性指定:@Component(value =”beanId” )

@Autowired注解自动装配

在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。

  • @Autowired:根据类型匹配,如果存在多个bean则会报错

    1
    2
    @Autowired
    private UserService userService;
  • @Autowired + @Qualifier

    1
    2
    3
    @Autowired
    @Qualifier("beanId")
    private UserService userService;

@Autowired中有属性required,默认值为true,因此在自动装配无法找到相应的bean时,会装配失败
可以将属性required的值设置为true,则表示能装就装,装不上就不装,此时自动装配的属性为默认值
但是实际开发时,基本上所有需要装配组件的地方都是必须装配的,用不上这个属性

AOP

各种通知

  • 前置通知:使用@Before注解标识,在被代理的目标方法前执行
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
  • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,环绕通知相当于上面四种通知加起来,所以使用了环绕通知就不使用上面四种通知

通知顺序

  • Spring版本5.3.x以前:
    • 前置通知
    • 目标操作
    • 后置通知
    • 返回通知或异常通知
  • Spring版本5.3.x以后:
    • 前置通知
    • 目标操作
    • 返回通知或异常通知
    • 后置通知

切面优先级

如果一个方法存在多个切面,可以在切面类上面添加org.springframework.core.annotation.Order.Order注解来标注切面的优先级,配置一个数字,数字越小,优先级越高,如果不指定值,则默认值为Integer类型的最大值

注解AOP使用

依赖

1
2
3
4
5
6
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<context:component-scan base-package="com.xiaofei.aop"/>

<!--基于注解的AOP的实现:
1、将目标对象和切面交给IOC容器管理(注解+扫描)
2、开启AspectJ的自动代理,为目标对象自动生成代理
3、将切面类通过注解@Aspect标识
-->
<aop:aspectj-autoproxy/>

</beans>

使用

1
2
3
4
5
6
7
8
9
10
public interface UserService {

int add(int i, int j);

int sub(int i, int j);

int mul(int i, int j);

int div(int i, int j);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class UserServiceImpl implements UserService {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部,result:"+result);
return result;
}

@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部,result:"+result);
return result;
}

@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部,result:"+result);
return result;
}

@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:"+result);
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
* 1、在切面中,需要通过指定的注解将方法标识为通知方法
* @Before:前置通知,在目标对象方法执行之前执行
* @After:后置通知,在目标对象方法的finally字句中执行
* @AfterReturning:返回通知,在目标对象方法返回值之后执行
* @AfterThrowing:异常通知,在目标对象方法的catch字句中执行
*
*
* 2、切入点表达式:设置在标识通知的注解的value属性中
* execution(public int com.atguigu.spring.aop.annotation.CalculatorImpl.add(int, int)
* execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..)
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示类中任意的方法
* ..表示任意的参数列表
* 类的地方也可以使用*,表示包下所有的类
* 3、重用切入点表达式
* //@Pointcut声明一个公共的切入点表达式
* @Pointcut("execution(* com.atguigu.spring.aop.annotation.CalculatorImpl.*(..))")
* public void pointCut(){}
* 使用方式:@Before("pointCut()")
*
* 4、获取连接点的信息
* 在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息
* //获取连接点所对应方法的签名信息
* Signature signature = joinPoint.getSignature();
* //获取连接点所对应方法的参数
* Object[] args = joinPoint.getArgs();
*
* 5、切面的优先级
* 可以通过@Order注解的value属性设置优先级,默认值Integer的最大值
* @Order注解的value属性值越小,优先级越高
*/
@Aspect
@Component
//@Order(1)
public class AspectTest {

@Pointcut("execution(* com.xiaofei.aop.service.UserService.*(..))")
public void pointCut(){}

//@Before("execution(public int com.xiaofei.aop.service.UserService.add(int, int))")
//@Before("execution(* com.xiaofei.aop.service.UserService.*(..))")
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint) {
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}

@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",执行完毕");
}

/**
* 在返回通知中若要获取目标对象方法的返回值
* 只需要通过@AfterReturning注解的returning属性
* 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",结果:"+result);
}

/**
* 在异常通知中若要获取目标对象方法的异常
* 只需要通过AfterThrowing注解的throwing属性
* 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
*/
@AfterThrowing(value = "pointCut()", throwing = "ex")
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex){
//获取连接点所对应方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:"+signature.getName()+",异常:"+ex);
}

@Around("pointCut()")
//环绕通知的方法的返回值一定要和目标对象方法的返回值一致
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("环绕通知-->前置通知");
//表示目标对象方法的执行
result = joinPoint.proceed();
System.out.println("环绕通知-->返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知-->异常通知");
} finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
}

配置文件AOP使用

Spring-Test使用

  • @RunWith(SpringJUnit4ClassRunner.class):指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
  • @ContextConfiguration(“classpath:ApplicationContext.xml”):指定配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Spring 测试相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>

<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件
@ContextConfiguration("classpath:ApplicationContext.xml")
public class SpringTest {

@Autowired
private JdbcTemplate template;
}

事务

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<dependencies>

<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>

<!-- Spring 持久化层支持jar包 -->
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 -->
<!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>

<!-- Spring 测试相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>

<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>

</dependencies>

数据库配置文件

1
2
3
4
5
jdbc.driver=com.mysql.cj.jdbc.Driver
# 如果是MySQL8.x需要加上serverTimezone=UTC
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
jdbc.username=root
jdbc.password=root

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 引入外部属性properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--开启组件扫描-->
<context:component-scan base-package="com.xiaofei.transactional"/>

<!--数据源配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!--配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!--
开启事务的注解驱动
将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
transaction-manager属性设置事务管理器的id
若事务管理器的bean的id默认为transactionManager,则该属性可以不写
-->
<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件
@ContextConfiguration("classpath:ApplicationContext.xml")
public class SpringTest {

@Autowired
private JdbcTemplate jdbcTemplate;

@Test
@Transactional//使用该注解,如果发生异常,数据回滚
public void insert() {
String sql = "insert into user(username, password, gender) values(?,?,?)";
jdbcTemplate.update(sql, 1, 1, 1);
int a = 10 / 0;
}
}

SpringMVC