0%

Mybatis基础大全

一、Mybatis

1.1 简介

  • 优秀的持久层框架

1.2 持久层

  • 持久化:将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失

二、第一个案例

2.1 导入依赖包与资源路径

2.1.1 导入依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--    mysql驱动    -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>

<!-- mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

<!-- junit -->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>

2.1.2 配置maven资源路径

  • 在pom文件中添加以下内容,防止项目运行时,代码层的xml文件由于不在默认的resource资源目录中,导致访问失败而报错,父/子模块的pom都可以添加进去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--  配置资源导出,防止dao层中的xml无法访问而报错  -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>

<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

2.2 配置Mybatis.xml文件

  • 在resource下创建Mybatis.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
<!--
由于hexo头信息无法复制,因此以注释的形式标注出来
<?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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8}"/>
<property name="username" value="${root}"/>
<property name="password" value="${FLzxSQC1998.Com}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>
</configuration>

2.3 编写代码

2.3.1 编写工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MybatisUtils {

// 提示作用域
private static SqlSessionFactory sqlSessionFactory;

static {
try {
// 使用Mybatis第一步:获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

}

// 有了工厂类SqlSessionFactory,就可以获取到SqlSession实例了
// SqlSession完全包含了面向数据库执行sql命令所需要的方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}

2.3.1 添加实体类

  • pojo层
1
2
3
4
5
public class User {
private Integer id;
private String name;
private String pwd;
}

2.3.2 编写接口

  • 无需编写实现类,Mybatis会帮我们实现
1
2
3
public interface UserDao {
List<User> getUserList();
}

2.4 Mapper配置文件

  • dao层下创建UserMapper.xml文件
  • 注意这个mapper文件需要在mybatis-config进行配置,否则程序运行会因为找不到mapper文件而报错
1
2
3
4
5
6
7
8
9
10
11
12
<!--
<?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">
-->
<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.kuang.dao.UserDao">
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user;
</select>
</mapper>

2.5 获取结果

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UserDaoTest {

@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);

List<User> userList = userDao.getUserList();

sqlSession.close();

}
}

三、增删改查

3.1 namespace

  • dao层命名统一为xxxMapper.xxx

3.2 Mapper配置文件解析

  • namespace:表示dao层定义的接口路径
  • id:表示接口中的方法名
  • resultType:Sql语句执行的返回值类型
  • parameterType:传入的参数类型
1
2
3
4
5
6
7
8
9
10
11
<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.kuang.dao.UserMapper">
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user
</select>

<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from mybatis.user where id= #{id}
</select>
</mapper>

3.3 CRUD

3.3.1 编写sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<mapper namespace="com.kuang.dao.UserMapper">
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from mybatis.user where id= #{id};
</select>

<!--插入用户-->
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd});
</insert>

<!--修改用户-->
<update id="updateUser" parameterType="com.kuang.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
</update>

<!--删除用户-->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id};
</delete>

</mapper>

3.3.2 编写接口方法

  • 在接口中定义对应方法
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface UserMapper {
// 根据id查找用户
User getUserById(int id);

// 插入用户
int addUser(User user);

// 修改用户
void updateUser(User user);

// 删除用户
void deleteUser(User user);
}

3.3.3 测试方法

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
// 查询测试
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

User user = userMapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}

// 插入测试
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.addUser(new User(4,"abc","abc"));
if(res > 0){
System.out.println("插入成功")
}

// 提交事务,否则无法实现插入效果
sqlSession.commit()
sqlSession.close();
}

// 修改测试
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.updateUser(new User(3,"abc","abc"));

// 提交事务,否则无法实现插入效果
sqlSession.commit()
sqlSession.close();
}

// 删除测试
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteUser(1);

// 提交事务,否则无法实现插入效果
sqlSession.commit()
sqlSession.close();
}

四、Map和模糊查询

4.1 Map实现CRUD

4.1.1 优势

  • 相对于原来的传pojo对象的方式,map方式更为传参更为灵活

4.1.2 编写sql语句

1
2
3
4
5
6
7
8
9
10
11
12
<mapper namespace="com.kuang.dao.UserMapper"> 
<!--
插入用户,使用map对象进行插入,注意parameterType="map",传递参数为map类型
values后的字段名可以自定义如userid等,map的键名必须跟这个自定的名字保持一致
这样就可以在map中取得对应的值,完成数据的插入
但表的列名字段必须和数据库表保持一致
-->
<insert id="addUser" parameterType="map">
insert into mybatis.user(id,name,pwd) values (#{userid},#{username},#{password});
</insert>

</mapper>

4.1.3 编写接口方法

1
2
3
4
public interface UserMapper {
// Map方式插入用户
int addUser(Map<String,Object> map);
}

4.1.4 测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

Map<String,Object> map = new HashMap<String,Object>();
map.put("userid",5);
map.put("username","123");
map.put("password","123");
userMapper.addUser(map);

System.out.println(user);
sqlSession.close();
}

4.2 模糊查询

4.2.1 编写sql语句

1
2
3
4
5
6
7
8
9
10
<mapper namespace="com.kuang.dao.UserMapper"> 
<!--
写成"%"#{value}"%"形式是为了防止sql注入的风险,推荐,这样只需要传递匹配字符就可以了
如果写成#{value}就必须传参"%xxx%",或者其他匹配符,否则报错,但不推荐这么写
-->
<select id="addUser" parameterType="map">
select * from mybatis.user where name like "%"#{value}"%";
</select>

</mapper>

五、环境配置

5.1 核心配置文件内容

  • 配置标签的位置顺序依次往下,不能随便写在某个位置,否则会报错
  • properties(属性)
  • setting(设置)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境配置)
    • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
  • databaseldProvider(数据库厂商标识)
  • mappers(映射器)

5.2 properties(属性)

  • 新建properties配置文件
1
2
3
4
5
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=FLzxSQC1998.Com

  • 在核心配置文件中引入外部配置文件,如db.properties
    • 需要注意的是,这个properties标签需要写在configuration标签下的第一个位置,因为configuration中的标签有顺序要求,否则会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--也可以在properties中配置对应的属性,但默认会先从外部的配置文件开始读取-->
<!--内部配置属性
<properties resource="db.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
-->

<properties resource="db.properties"/>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--配置时,使用${}取到properties配置文件中对应的值-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

5.3 typeAliases(类型别名)

5.3.1 自定义别名

  • 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
  • 设置完简化后的别命,在使用时可以直接使用,比如在sql语句配置文件的returnType就可以直接使用别名,使配置更加简洁清晰
  • 当这样配置时,别名可以用在任何使用的地方,这种方式一般在实体类较少的情况下使用
1
2
3
4
5
6
7
8
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

5.3.2 扫描包生成别名

  • 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
  • 这种可以在实体类比较多的时候使用
1
2
3
4
5
6
7
<!--
设置包后,会自动为对应的类设置别命,别名即为类名
这种情况,设置的别名为首字母小写的类名,比如domain.blog.Author 的别名为 author
-->
<typeAliases>
<package name="domain.blog"/>
</typeAliases>

5.3.3 注解别名

  • 还可以使用注解设置别名
  • 在使用扫描包生成别名时,如果非要改成自定义的,可以在对应类上加上注解
1
2
3
4
@Alias("author")
public class Author {
...
}

5.3.4 基本类型的别名

  • 基本类型前加下划线:如int别名:_int
  • 包装类型首字母小写:如Integer别名:int

5.4 setting(设置)

5.5 其他配置

5.6 mappers(映射器)

  • 在核心配置文件中配置mapper配置文件所在路径
  • 方式一:使用mapper文件绑定【推荐】
1
2
3
4
5
6
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
  • 方式二:使用class绑定
1
2
3
4
5
6
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
  • 使用除方式一外的其他方式时的注意点:
    • 接口和他的Mapper配置文件必须同名
    • 接口和他的Mapper配置文件必须在同一包下

六、生命周期和作用域

6.1 SqlSessionFactoryBuilder

  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了

6.2 SqlSessionFactory

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 类似数据库连接池

6.3 SqlSession

  • 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 连接到连接池的一个请求
  • 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中
  • 每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它
1
2
3
4
5
6
7
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}finally{
session.close();
}


6.4 获取映射实例

1
2
3
4
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}

七、ResultMap结果集映射

7.1 简介

  • 解决实体类属性名和数据库表字段名不一致的问题

7.2 结果映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
结果映射标签
property对应了实体类属性,column对应表字段
-->
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>

<!--这里的返回类型使用resultMap标注,值为resultMap标签的id-->
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>

八、日志

8.1 日志工厂

  • 如果一个数据库操作出现了异常,我们最好是能看到操作的sql语句是啥,方便我们排错
  • 因此针对以上问题,我们可以使用日志的形式输出我们数据库操作的sql语句
  • Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
    • SLF4J
    • LOG4J【掌握】
    • LOG4J2
    • JDK_LOGGING
    • COMMONS_LOGGING
    • STDOUT_LOGGING【掌握】
    • NO_LOGGING

8.2 日志配置

  • 需要在核心配置文件中进行配置
  • 配置完后直接运行即可,就可以在控制台输出日志信息
1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

8.3 Log4j

8.3.1 核心配置

1
2
3
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>

8.3.2 导入Log4j

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

8.3.3 log4j配置文件

  • 在resources下创建log4j.properties配置文件
  • 这里面可以配置许多日志的输出格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
### 设置###
log4j.rootLogger = debug,stdout,D,E

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

8.3.4 Log4j的使用

  • 直接运行就可以在控制台输出日志

  • 也可以编写语句

1
2
3
4
5
6
7
8
9
public class UserDaoTest {
// 生成logger对象
static Logger logger = Logger.getLogger(String.valueOf(UserDaoTest.class));

@Test
public void testLog4j(){
logger.info("info:进入了testLog4j");
}
}

九、分页实现

9.1 Limit分页

1
SELECT * FROM user LIMIT startIndex,pageSize;

9.2 Mybatis分页

9.2.1 定义接口方法

1
2
3
4
public interface UserMapper {
List<User> getUserByLimit(Map<String,Integer> map);
}

9.2.1 mapper配置文件

1
2
3
<select id="getUserByLimit" parameterType="map" resultType="com.kuang.pojo.User">
select * from mybatis.user limit #{startIndex},#{pageSize};
</select>

9.2.3 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

Map<String,Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);

List<User> users = userMapper.getUserByLimit(map);
for (User user : users){
System.out.println(user);
}

sqlSession.close();
}

十、注解开发

  • 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心
  • 本质:反射机制
  • 底层:动态代理
1
2
3
4
5
public interface UserMapper{
@Select("select * from user")
List<User> getUsers();
}

十一、Mybatis执行流程

十二、Lombok

十三、复杂查询处理

13.1 数据库环境

  • 学生表:id、name、tid(外键)
  • 教师表:id、name
  • 一个教师教多个学生:一对多
  • 多个学习选同一个老师:多对一
  • 学生类
1
2
3
4
5
6
@Data
public class Student {
private Integer id;
private String name;
private Teacher teacher;
}
  • 教师类
1
2
3
4
5
6
@Data
public class Teacher {
private Integer id;
private String name;
private List<Student> students;
}

13.1 多对一处理

13.1.1 方式一:查询嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--绑定命名空间,绑定自己的dao层接口-->
<mapper namespace="com.kuang.dao.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select * from student;
</select>

<resultMap id="StudentTeacher" type="com.kuang.pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="com.kuang.pojo.Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="com.kuang.pojo.Teacher">
select * from teacher where id = #{tid};
</select>
</mapper>

13.1.2 方式二:结果嵌套

  • 连表查询,不想方式二分成两个查询语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<mapper namespace="com.kuang.dao.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid = t.id;
</select>

<!--这里的column应该是上面select语句定义的别名-->
<resultMap id="StudentTeacher" type="com.kuang.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>

<association property="teacher" javaType="com.kuang.pojo.Teacher">
<result property="name" column="tname"/>
</association>

</resultMap>
</mapper>

13.1.3 结果

  • 以上两种方式的结果输出相同

13.2 一对多处理

13.2.1 方式一:查询嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<mapper namespace="com.kuang.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid};
</select>
<resultMap id="TeacherStudent" type="com.kuang.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>

<collection property="students" ofType="com.kuang.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>

</resultMap>
</mapper>

13.2.2 方式二:结果嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mapper namespace="com.kuang.dao.TeacherMapper">
<select id="getTeacher" resultMap="TeacherStudent">
select * from teacher where id = #{tid};
</select>

<resultMap id="TeacherStudent" type="com.kuang.pojo.Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudents"/>
</resultMap>

<select id="getStudents" resultType="com.kuang.pojo.Student">
select * from student where tid = #{id};
</select>
</mapper>

13.2.3 结果

  • 以上两种方式结果相同

13.3 小结

  • 多对一:association(关联)
  • 一对多:collection(集合),和上面都是在returnMap结果映射中使用
  • javaType:指定实体类中属性的类型
  • ofType:指定映射到List或集合中的pojo类型,泛型中约束的类型(一般在collection中会使用到)
  • 在没有为pojo类起别名时,returnType直接使用类名会报错ClassNotFound,此时必须包的全类名
  • colume属性对应的是select语句指定的导出表的列名,如果有列的别名,就写别名
  • 以上两种方式都可实现效果,视情况而定实现方式

十四、动态Sql

14.1 简介

  • 对sql语句添加条件判断,根据条件判断生成不同的sql语句

14.2 动态sql标签

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach
  • sql

14.3 if

  • if表示如果查询的map中存在这个字段,就拼接if中的语句,否则不进行拼接
1
2
3
4
5
6
7
8
<select id="findActiveBlogWithTitleLike" parameterType="map"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
  • 这种语句会存在一种缺陷,它只是机械的语句拼接

  • 下面这种情况就可能拼接出:SELECT FROM BLOG WHERE AND title like #{title}或者SELECT FROM BLOG WHERE

    • 这就造成了sql语句的语法错误,无法完成查询,需要用到下面的where标签
    • where标签可以自动的去除一些字段,比如多余的AND或者符号“,”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>

14.4 choose

  • choose类似于java中的switch,选择语句,满足其中一条则拼接对应的语句
  • 如果都不满足则拼接otherwise中的语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>

14.5 trim(where、set)

14.5.1 where

  • 将所有匹配条件放入到where标签中
  • 解决了if标签的机械的语句拼接问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>

14.5.2 set

  • 完成update的操作标签
  • 也是用if进行条件判断拼接sql语句
1
2
3
4
5
6
7
8
9
10
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>

14.5.3 trim

  • trim可以完成一些自定义的操作
  • 比如自定where和set标签,意思就是trim可以替换where和set标签
1
2
3
4
5
6
7
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>

<trim prefix="SET" suffixOverrides=",">
...
</trim>

14.6 foreach

  • 这里是从POST表中查询需要的多条数据
  • 这里的collection指定的是我们传入的列表,这里返回的就是列表中指定的id对应的数据
  • 加入说list中的id列表为(1,2,3),那么就将满足这些id的数据全部获取到:(id=1,name=…)、(id=2,name=…)、(id=3,name=…)
1
2
3
4
5
6
7
8
9
10
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>

14.7 sql

  • 用于封装动态sql语句
  • 使用include标签引入外部sql标签的id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<sql id="if-title-author">
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</sql>

<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<include refid="if-title-author"/>
</where>
</select>

十五、缓存

15.1 缓存介绍

  • 多次重复查询会消耗服务器连接数据库的资源
  • 缓存:存放一次查询的结果,下次查询就不用查数据库了
  • 使用缓存的情况:经常查询且不经常改变的数据

15.2 一级缓存

15.2.1 简介

  • 一级缓存是默认开启的
  • 只在一次SqlSession中有效,也就是SqlSession打开到关闭之间,进行查询就会存在缓存,下次查询相同数据就不会再执行一次sql语句,直接从缓存中抽取数据

15.2.2 缓存失效的情况

  • 查询不同的内容
  • 增删改操作会改变原来的数据,所以必定会刷新缓存
  • 查询不同的Mapper.xml
  • 手动清理缓存

15.3 二级缓存

15.3.1 简介

  • 二级缓存也叫全局缓存,这是由于一级缓存的作用域太低,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间对应一个二级缓存
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没有了,现在需要的是,会话关闭时,一级缓存的数据可以保存到二级缓存中
    • 新的会话查询,就可以从二级缓存中获取内容
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

15.3.2 开启二级缓存

  • 在核心配置文件添加以下标签,开启全局缓存
1
2
3
4
<settings>
<!--显示开启全局缓存,默认是开启的-->
<setting name="cacheEnabled" value="true"></setting>
</settings>
  • 配置完后还需要再需要使用二级缓存的Mapper配置文件下添加cache标签
    • eviction为缓存策略
    • flushInterval为缓存刷新时间
    • size为最大数引用数
    • readOnly为是否只读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mapper namespace="xxx">
<!--在当前mapper下开启二级缓存-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!--也可以直接使用cache标签-->
<cache/>

<select useCache="true">
...
</select>

</mapper>
  • 使用cache标签,不添加缓存策略时,需要将实体类实现序列化,如下
1
2
3
public class User implements Serializable{
...
}

15.3.3 小结

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中
    • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中

15.4 缓存原理

  • 每个SqlSession从开启到关闭都会启动一个一级缓存
  • SqlSession关闭后一级缓存会升级为二级缓存
  • 用户进行查询时会先看二级缓存是否存在查询的内容,再去一级缓存,缓存中没有要查找的对象时,就执行查询数据库的操作

15.5 自定义缓存

15.5.1 ehcache

  • EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

15.5.2 引入依赖

1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>

15.5.3 cache配置

1
2
3
<mapper namespace="xxx">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>

15.5.4 ehcache配置文件

十六、多表查询