0%

高级特性

高级特性,包括关联查询、延迟加载、动态SQL、缓存、逆向工程、PageHelper分页插件

1.关联查询

1.一对一

例子:一个order订单对应一个user

需要创建专门的结果映射类,OrderExt,里边有User属性。

1
2
3
4
public class OrdersExt extends Orders {
private User user;// 用户对象
// get/set。。。。
}

resultMap配置普通的OrderExp的属性+User属性

  • ​ 普通属性直接用result

  • ​ User属性用association内嵌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
<!-- 查询订单关联用户信息使用resultmap -->
<resultMap type="OrdersExt" id="ordersAndUserRstMap">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 一对一关联映射 -->
<!--
property:Orders对象的user属性
javaType:user属性对应 的类型
-->
<association property="user" javaType="com.kkb.mybatis.po.User">
<!-- column:user表的主键对应的列 property:user对象中id属性-->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="address" property="address"/>
</association>
</resultMap>
<select id="findOrdersAndUserRstMap" resultMap="ordersAndUserRstMap">
SELECT
o.id,
o.user_id,
o.number,
o.createtime,
o.note,
u.username,
u.address
FROM
orders o
JOIN `user` u ON u.id = o.user_id
</select>

2.一对多

例子:一个用户有多个订单

User类中有List<Orders>

1
2
3
4
5
6
7
8
public class User{
private Integer id;
private String username;
private String sex;
private Date birthday;
private String address;
private List<Orders> orders;
}

resultMap配置User的普通属性+List<Orders>属性

  • 普通属性用result
  • List<Orders>用collection,collection的返回类型用ofType配置为List内的泛型orders
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
<resultMap type="user" id="userAndOrderRstMap">
<!-- 用户信息映射 -->
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!-- 一对多关联映射 -->
<collection property="orders" ofType="orders">
<id property="id" column="oid"/>
<result property="userId" column="id"/>
<result property="number" column="number"/>
<result property="createtime" column="createtime"/>
<result property="note" column="note"/>
</collection>
</resultMap>
<select id="findUserAndOrderRstMap" resultMap="userAndOrderRstMap">
SELECT
u.*,
o.id oid,
o.number,
o.createtime,
o.note
FROM
`user` u
LEFT JOIN orders o ON u.id = o.user_id
</select>

2.延迟加载

延迟加载,也称为懒加载,关联查询时,推迟查询关联对象的查询。

目的:减少数据库的压力。

分类:直接加载、侵入式加载、深度延迟加载

存在的问题:深度加载存在N+1的问题。深度加载的实现是主查询+N次子查询,会发送N次SQL子查询语句。子查询记录比较多时,发送SQL子查询语句很消耗资源。

使用:需要resultMap中的association和collection子标签,主配置文件中settings配置延迟加载lazyLoadingEnabledaggressiveLazyLoading,resultMap子标签也可开启延迟加载属性fetchType,子标签的fetchType可覆盖全局属性

直接加载:

1
2
3
4
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="false"/>
</settings>

侵入式延迟加载:

1
2
3
4
5
6
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>

深度延迟加载:

1
2
3
4
5
6
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

3.缓存

1.一级缓存

默认开启,作用域:同一个sqlsession中。修改和删除会清除缓存。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);
User user = new User();
user.setUsername("隔壁老詹1");
user.setAddress("洛杉矶湖人");
//执行增删改操作,清空缓存
mapper.insertUser(user);
// 第二次查询ID为1的用户
User user2 = mapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}

2.二级缓存

默认不开启,作用域:同一个Mapper(namespace)中。不同的sqlsseion可查询到同一个Mapper缓存中的数据。

开启方式:全局配置settings+namespace配置。

1
2
3
4
<!-- 开启二级缓存总开关 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
1
2
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
<cache></cache>

一般不用,二级缓存控制粒度较粗,同一个namespace中执行任何增删改都会清除缓存。

二级缓存的数据不一定都是存储到内存中 ,比如文件系统 ,二级缓存相关的类和父类需要实现序列化。

一种办法:namespace中只存放查询语句。不清楚其他地方修改了数据库中此namespace中的记录,是否会读到脏数据?

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper1.findUserById(1);
System.out.println(user1);
// 关闭SqlSession1
sqlSession1.close();
// 第二次查询ID为1的用户
User user2 = mapper2.findUserById(1);
System.out.println(user2);
// 关闭SqlSession2
sqlSession2.close();
}

4.动态SQL

解决:字符串的拼接处理、循环判断。

1.if标签

queryvo中封装有user对象

1
2
3
4
5
6
7
8
<select id="findUserList" parameterType="queryVo" resultType="user">
SELECT * FROM user where 1=1
<if test="user != null">
<if test="user.username != null and user.username != ''">
AND username like '%${user.username}%'
</if>
</if>
</select>

2.where标签

解决 if标签内容开头规定为AND,where 后需要 1 = 1,解决1=1的性能问题 。1=1时不使用索引

1
2
3
4
5
6
7
8
9
10
11
<select id="findUserList" parameterType="queryVo" resultType="user">
SELECT * FROM user
<!-- where标签会处理它后面的第一个and -->
<where>
<if test="user != null">
<if test="user.username != null and user.username != ''">
AND username like '%${user.username}%'
</if>
</if>
</where>
</select>

3.SQL片段复用

include标签,提取可复用代码

1
2
3
4
5
6
7
<sql id="query_user_where">
<if test="user != null">
<if test="user.username != null and user.username != ''">
AND username like '%${user.username}%'
</if>
</if>
</sq

使用include标签

1
2
3
4
5
6
7
8
9
<!-- 使用包装类型查询用户 使用ognl从对象中取属性值,如果是包装对象可以使用.操作符来取内
容部的属性 -->
<select id="findUserList" parameterType="queryVo" resultType="user">
SELECT * FROM user
<!-- where标签会处理它后面的第一个and -->
<where>
<include refid="query_user_where"></include>
</where>
</select>

引用其他Mapper标签

1
<include refid="namespace.sql片段”/>  

4.foreach标签

需求:综合查询时,传入多个id查询用户信息

1
SELECT * FROM USER WHERE username LIKE '%老郭%' AND id IN (1,10,16)

pojo中添加List<Interger> ids属性

1
2
3
4
public class QueryVo {
private User user;
private List<Interger> ids;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<sql id="query_user_where">
<if test="user != null">
<if test="user.username != null and user.username != ''">
AND username like '%${user.username}%'
</if>
</if>
<if test="ids != null and ids.size() > 0">
<!-- collection: 指定输入的集合参数的参数名称 -->
<!-- item: 声明集合参数中的元素变量名 -->
<!-- open: 集合遍历时,需要拼接到遍历sql语句的前面 -->
<!-- close: 集合遍历时,需要拼接到遍历sql语句的后面 -->
<!-- separator: 集合遍历时,需要拼接到遍历sql语句之间的分隔符号 -->
<foreach collection="ids" item="id" open=" AND id IN ( "
close=" ) " separator=",">
#{id}
</foreach>
</if>
</sql>

5.逆向工程

功能:可以依据数据库表生成User实体类、UserExample扩展类、UserMapper接口、UserMapper.xml。UserMapper.xml包括增删改查。不可自动实现部分:一对一、一对多映射。

如何使用:导入逆向工程项目,配置生成的各个类的路径,执行逆向工程main函数。

配置文件:

  1. 修改要生成的数据库表

  2. pojo文件所在包路径

  3. Mapper所在的包路径

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator><!-- 是否去除自动生成的注释 true: 是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/ssm" userId="root"
password="root">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg" password="yycg"> </jdbcConnection> -->
<!-- 默认false, 把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer, 为 true时
把JDBC DECIMAL和 NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="com.kkb.ms.po" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.kkb.ms.mapper" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage: mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.kkb.ms.mapper" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table schema="" tableName="user"></table>
<table schema="" tableName="order"></table>
</context>
</generatorConfiguration>

6.分页插件PageHelper

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md

功能:分页

原理:实现Mybatis预留的分页接口。

使用:

​ 导入jar包

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

配置全局配置文件plugins

1
2
3
4
5
6
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- config params as the following -->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//获取第1页, 每页10条内容,默认查询总数count
PageHelper.startPage(1, 10);
//增加上边一行后,此处的返回的list变为PageHelper提供的Page对象。
List<Country> list = countryMapper.selectAll();
//用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
//测试PageInfo全部属性
//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());

注意:

​ 查询语句是使用resultMap进行的嵌套结果映射,则无法使用PageHelper进行分页

PageHelper 安全调用

  • 不安全:

    1
    2
    3
    4
    5
    6
    7
    PageHelper.startPage(1, 10);
    List<User> list;
    if(param1 != null){
    list = userMapper.selectIf(param1);
    } else {
    list = new ArrayList<User>();
    }
  • 安全:

    1
    2
    3
    4
    5
    6
    7
    List<User> list;
    if(param1 != null){
    PageHelper.startPage(1, 10);
    list = userMapper.selectIf(param1);
    } else {
    list = new ArrayList<User>();
    }