MyBatis映射文件深入

动态sql

可以看到,在之前的映射文件中,所有sql语句都是写死的,并不会根据我传入参数的不同进行区分,但在实际开发过程中,可能需要执行sql语句查询前先进行逻辑判断或其他操作,对参数进行简单的判断

例如下面的简单情况,我们在映射文件中写明了查询的条件,需要你User对象传入三个参数

1
2
3
<select id="findByCondition" parameterType="user" resultType="user">
select * from user where id=#{id} and username=#{username} and password=#{password}
</select>

此时,传入的User对象若存在该三个属性值则能够进行正常的查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test() throws IOException {
User user=new User();
user.setId((long) 4);
user.setUsername("lily");
user.setPassword("1234");
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findByCondition(user);
System.out.println(users);
}
/* 执行结果
[User{id=4, username='lily', password='1234'}]
*/

若我们传入的User没有任一属性值,则不能正确查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test() throws IOException {
User user=new User();
user.setId((long) 4);
//user.setUsername("lily");
user.setPassword("1234");
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findByCondition(user);
System.out.println(users);
}
/* 执行结果为[]
因为缺少属性值username所以查询语句变成了
select * from user where id=4 and username=null and password=1234
而不存在username为空的数据,所以返回空集合
*/

很明显这不是我们想要达成的业务逻辑,所以我们可以采用动态sql的方式,通过提供的标签进行简单的逻辑判断

if标签

if标签用于进行判断逻辑操作,使用方式如下,分别判断各个属性值是否为空,若不为空则将对应语句添加到原语句的后面,若为空则不添加,这里的where标签与数据库中的where语法一致,只是它可以自动合理的帮我们拼接多个if条件语句,并且如果所有条件均不满足,where不会拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findByCondition" parameterType="user" resultType="user">
select * from user
<where>
<if test="id!=0">
and id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
<if test="password!=null">
and password=#{password}
</if>
</where>
</select>

使用后,原测试用例在省略参数后也可以正常查询到符合条件的数据

foreach标签

在实际应用中,我们还可能遇到查询条件不唯一的情况,例如所有编号为1或2或3的查询结果,如果单纯利用sql语句的方式可以写为SELECT * FROM user WHERE id IN (1,2,3),而实际开发中这个list集合一般是由service层传递给mapper层作为参数进行查询,此时就需要用到foreach标签进行集合的遍历

foreach标签的属性值较多,其分别代表:

  • collection:集合类型,可以为list或array
  • item:表示遍历的元素的名称
  • open:语句开头的内容,根据sql语句进行填写
  • close:语句结束的部分,同样根据sql语句进行填写即可
  • separator:元素之间的分隔符,分割每个遍历元素的
  • 标签体中写元素格式即可
    1
    2
    3
    4
    5
    6
    7
    8
    <select id="findByList" parameterType="list" resultType="user">
    select * from user
    <where>
    <foreach collection="list" item="id" open="id in (" close=")" separator=",">
    #{id}
    </foreach>
    </where>
    </select>

foreach标签的拼接结果是id IN (1,2,3) 再利用where标签将其与原语句拼接后得到SELECT * FROM user WHERE id IN (1,2,3)

sql片段的抽取

对于配置文件中高度重复的sql语句片段,我们可以利用抽取的思想对语句片段进行抽取,方便复用和修改

1
2
3
4
5
6
7
8
9
10
11
12
<!--sql语句的抽取-->
<sql id="selectAll">select * from user</sql>

<!--使用抽取的sql语句-->
<select id="findByList" parameterType="list" resultType="user">
<include refid="selectAll"></include>
<where>
<foreach collection="list" item="id" open="id in (" close=")" separator=",">
#{id}
</foreach>
</where>
</select>

MyBatis核心配置文件深入

typeHandler-类型转换器

当我们从数据库获取数据或将数据写入数据库的过程中,始终存在类型转换的过程,例如Java中的Integer到数据库中的int或Java中的String到数据库中的varchar,这些基本数据类型的转换MyBatis已经有自己默认的类型转换器,一般情况下不需要我们处理,但当我们要处理自己定义的类型或MyBaitis没有默认处理的类型时,就需要自己定义类型转换器(例如将日期类型转换为毫秒值存入数据库,再在读取数据时将毫秒值转为日期)

typeHandler使用步骤

这里实现了将Date类型存入数据库的过程中转换为毫秒值传入,并在从数据库读取该数据时重新转换为Date类型存入User对象

1. 定义转换类继承类BaseTypeHandler并实现方法

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
public class DateTypeHandler extends BaseTypeHandler<Date> {
//将Java类型转换为数据库所需的类型,参数i表示将转换后数据插入的位置,字段躲在的列
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
//将日期转为长整型
long time = date.getTime();
//设置参数
preparedStatement.setLong(i,time);
}

//将数据库类型转换为Java所需的类型
//s参数表示数据表字段的名称,resultSet是查询结果集
@Override
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
//获取数据库中的数据
Long dateTime=resultSet.getLong(s);
//转化为Java中的Date类型
Date date=new Date(dateTime);
//返回数据
return date;
}

//将数据库类型转换为Java所需的类型
@Override
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
//获取数据库中的数据
Long dateTime=resultSet.getLong(i);
//转化为Java中的Date类型
Date date=new Date(dateTime);
//返回数据
return date;
}

//将数据库类型转换为Java所需的类型
@Override
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
//获取数据库中的数据
Long dateTime=callableStatement.getLong(i);
//转化为Java中的Date类型
Date date=new Date(dateTime);
//返回数据
return date;
}
}

2. 在MyBatis核心配置文件中配置转换器

1
2
3
4
<!--自定义类型转换器-->
<typeHandlers>
<typeHandler handler="cn.ywrby.handler.DateTypeHandler"/>
</typeHandlers>

3. 测试转换效果

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 test() throws IOException {
User user=new User();
user.setId((long) 6);
user.setUsername("Kelly");
user.setPassword("1234");
//获取当前时间并作为参数传入
user.setDate(new Date());
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试能否将Date类型转为毫秒值保存到数据库
mapper.save(user);
sqlSession.commit();
//测试从数据库读取数据能否正确转换为Java中的Date类型
List<User> userList = mapper.findByCondition(user);
System.out.println(userList);
sqlSession.close();
}
/* 运行结果
[User{id=6, username='Kelly', password='1234', date=Sat Mar 20 16:30:36 CST 2021}]
*/

数据库显示效果

可以看到首先成功将Date类型存入数据库的值变为毫秒值,而从数据读取到Java过程中又转换回了Date类型

plugins-插件标签

MyBatis可以使用第三方插件来进行功能的扩展,这里以分页助手(page-helper)为例进行插件使用的演示,其功能是将复杂的分页技术进行封装,使用简单的方式即可获取分页数据

插件使用步骤

  1. 导入插件的坐标
  2. 在mybatis核心配置文件中配置插件
  3. 测试分页数据的获取

导入page-helper的坐标

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.3</version>
</dependency>

在核心配置文件中配置插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--
plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--配置数据库方言-->
<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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Test
public void test() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

//设置分页相关参数 当前页,每页显示的条数
PageHelper.startPage(1,3);

List<User> userList=mapper.findAll();
for (User user:userList){
System.out.println(user);
}

//获得与分页相关的参数
PageInfo<User> pageInfo=new PageInfo<User>(userList);
//输出相关信息
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("上一页:"+pageInfo.getPrePage());

sqlSession.close();
}
/* 运行结果
User{id=1, username='Leslie', password='123', date=null}
User{id=2, username='Jessica', password='123', date=null}
User{id=4, username='lily', password='1234', date=null}
当前页:1
总条数:6
总页数:2
上一页:0
*/

可以看到,数据按照指定的第一页显示三条进行了输出,并且可以通过PageInfo对象获取所有的分页信息