0. 工程搭建
还是一样,我们先来搭建一个新的工程 mybatis-04-annotation
,从上一个工程中把文件都复制粘贴过来,精简一下。
精简的代码,只需要精简一下 mybatis-config.xml
即可:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<package name="com.linkedbear.mybatis.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/department.xml"/>
<mapper resource="mapper/user.xml"/>
</mappers>
</configuration>
其余的代码可根据自己实际练习自由精简,小册没有做过多的精简,只是删了一些多余的 resultMap 而已。
另外,我们还需要创建两个新的 Mapper 接口,分别代替原本的 DepartmentMapper
和 UserMapper
:
public interface DepartmentAnnotationMapper {
}
public interface UserAnnotationMapper {
}
创建完成后记得配置到 MyBatis 全局配置文件中:
<mappers>
<mapper resource="mapper/department.xml"/>
<mapper resource="mapper/user.xml"/>
<mapper class="com.linkedbear.mybatis.mapper.DepartmentAnnotationMapper"/>
<mapper class="com.linkedbear.mybatis.mapper.UserAnnotationMapper"/>
</mappers>
OK ,下面我们准备开始。MyBatis 3.0 版本出现了基于注解驱动的映射器,我们可以使用注解的方式定义 statement ,这样似乎更简单方便,但由于 Java 语言特性的一些局限性,MyBatis 没有特别好的办法构建出非常强大的结果集映射(没办法跟 mapper.xml 里那样那么灵活的配置 resultMap )。不过基于注解的 statement 定义还是提供了一种方便且成本低的方式。
本章我们先来回顾一下 MyBatis 中基本的注解使用。
1. @Select
@Select
注解,类比于 mapper.xml 中最常见到的 <select>
标签,它算是最基础的注解之一了。我们可以先来举几个简单的例子,帮助各位快速回顾和上手 @Select
注解的使用。
1.1 基本单表查询
简单的单表查询那是再简单不过了,只需要声明一个 findAll
方法,并在方法上标注 @Select
注解,写好 SQL 就完事了:
public interface DepartmentAnnotationMapper {
@Select("select * from tbl_department")
List<Department> findAll();
}
别看这样简单,其实它这样写完之后,相比较于 mapper.xml 中定义一个 statement 需要提供的要素,简直是一个也不少:
findAll
→ select 的 idList<Department>
→ resultTypeDepartmentAnnotationMapper
的全限定类名 → namespace- SQL
这样写完之后,我们就可以直接编写测试类来运行了:
public class AnnotationMapperApplication {
public static void main(String[] args) throws Exception {
InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);
SqlSession sqlSession = sqlSessionFactory.openSession();
DepartmentAnnotationMapper departmentMapper = sqlSession.getMapper(DepartmentAnnotationMapper.class);
List<Department> departmentList = departmentMapper.findAll();
departmentList.forEach(System.out::println);
}
}
运行 main
方法,控制台可以正常打印所有的部门信息:
Department{id='00000000000000000000000000000000', name='全部部门', tel='-'}
Department{id='18ec781fbefd727923b0d35740b177ab', name='开发部', tel='123'}
Department{id='53e3803ebbf4f97968e0253e5ad4cc83', name='测试产品部', tel='789'}
Department{id='ee0e342201004c1721e69a99ac0dc0df', name='运维部', tel='456'}
说明一切可行。
1.2 附带入参
与之前用 Mapper 动态代理开发的套路一样,在 Mapper 接口上声明方法形参,并在 SQL 中引用参数即可:
@Select("select * from tbl_department where id = #{id}")
Department findById(String id);
同样的,我们写一下测试代码:
Department department = departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");
System.out.println(department);
运行 main
方法测试,控制台可以成功打印:
Department{id='18ec781fbefd727923b0d35740b177ab', name='开发部', tel='123'}
1.3 复杂SQL编写
findAll
方法是最基础的单表查询了,不过更多的情况,是根据一个 example 对象,动态的拼接 SQL 了。但这里面问题就来了:@Select
注解中咋进行判断呢?
很不幸,这就是 MyBatis 官方文档中说的,他们实在没办法搞定这种复杂 SQL 的编写了,只能利用类似于脚本的形式,编写与 mapper.xml 中类似的动态 SQL 。小册先举一个例子:
@Select("<script>select * from tbl_department "
+ "<where>"
+ "<if test='id != null'>and id = #{id} </if>"
+ "<if test='name != null'>and name like concat('%', #{name}, '%') </if>"
+ "<if test='tel != null'>and tel = #{id} </if>"
+ "</where>"
+ "</script>")
List<Department> findAllByExample(Department example);
嚯,这看着也太难受了吧!没有提示就算了,这字符串拼的,看都没法看。。。所以这也就是为什么 MyBatis 官方说复杂 SQL 不推荐用注解映射器编写。
好啦,抛开辣眼不谈,我们先测试一下效果如何:
Department example = new Department();
example.setName("全部");
List<Department> departmentListByExample = departmentMapper.findAllByExample(example);
System.out.println(departmentListByExample);
运行测试代码,控制台打印的 SQL 和查询结果均符合预期:
[main] DEBUG otationMapper.findAllByExample - ==> Preparing: select * from tbl_department WHERE name like concat('%', ?, '%')
[main] DEBUG otationMapper.findAllByExample - ==> Parameters: 全部(String)
[main] DEBUG otationMapper.findAllByExample - <== Total: 1
[Department{id='00000000000000000000000000000000', name='全部部门', tel='-'}]
1.4 手动配置结果集映射
与 mapper.xml 一样,如果查询的结果需要我们手动配置结果集映射的话,用注解也是可以的,但是配置方式又是比较辣眼的。。。
1.4.1 配置示例
下面小册先来写一个示例:
@Select("select * from tbl_department")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "name", column = "tel"),
@Result(property = "tel", column = "name")
})
List<Department> findAllByResults();
如上述代码所示,@Results
注解可以类比 <resultMap>
标签,每个 @Result
注解可以类比 <id>
或者 <result>
标签。对于简单的结果集映射来讲,这种写法还是可以接受的。
下面我们依然可以编写一下测试代码来验证一下配置的结果集映射是否生效:
List<Department> departmentByResultsList = departmentMapper.findAllByResults();
departmentByResultsList.forEach(System.out::println);
运行 main
方法,发现控制台中打印的 Department
对象的 name
属性与 tel
确实对调了,说明这样配置没有问题。
Department{id='00000000000000000000000000000000', name='-', tel='全部部门'}
Department{id='18ec781fbefd727923b0d35740b177ab', name='123', tel='开发部'}
Department{id='53e3803ebbf4f97968e0253e5ad4cc83', name='789', tel='测试产品部'}
Department{id='ee0e342201004c1721e69a99ac0dc0df', name='456', tel='运维部'}
1.4.2 结果集映射复用
可能细心的小伙伴发现了一个问题:以上面的配置方式配置结果集映射,那岂不是每个 @Select
都需要写一遍吗?在早期的 MyBatis 中它确实需要这样,不过好在 MyBatis 3.3.1 版本以后,@Results
注解多了一个 id
属性,这样就可以通过使用另一个注解 @ResultMap
,并声明 id
属性引用已经定义好的结果集映射,以此实现结果集映射的复用了。
@Select("select * from tbl_department")
@Results(id = "departmentUseResultsId", value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "name", column = "tel"),
@Result(property = "tel", column = "name")
})
List<Department> findAllByResults();
@Select("select * from tbl_department")
@ResultMap("departmentUseResultsId")
List<Department> findAll();
然后我们可以再测试一下 findAll
方法的效果,这里小册只贴出测试后控制台打印的日志和运行结果,可以看出 resultMap 确实已经成功引用了:
[main] DEBUG rtmentAnnotationMapper.findAll - ==> Preparing: select * from tbl_department
[main] DEBUG rtmentAnnotationMapper.findAll - ==> Parameters:
[main] DEBUG rtmentAnnotationMapper.findAll - <== Total: 5
Department{id='00000000000000000000000000000000', name='-', tel='全部部门'}
Department{id='18ec781fbefd727923b0d35740b177ab', name='123', tel='开发部'}
Department{id='53e3803ebbf4f97968e0253e5ad4cc83', name='789', tel='测试产品部'}
Department{id='ee0e342201004c1721e69a99ac0dc0df', name='456', tel='运维部'}
当然,如果实际开发中有使用 xml + 注解
的混合开发时,@ResultMap
注解也可以使用 namespace + resultMap id
的全限定名方式引用 mapper.xml 中定义的 resultMap 。
2. DML注解
下面我们再用一下 DML 语句对应的 @Insert
、@Update
、@Delete
注解。
2.1 @Insert
2.1.1 基本使用
@Insert
注解本身的使用是很简单的,我们可以快速写一个测试代码试一下:
@Insert("insert into tbl_department (id, name, tel) values (#{id}, #{name}, #{tel})")
int save(Department department);
对应的,测试的时候需要先构造一个 Department
,然后执行 save
方法即可(不要忘记最后提交事务):
Department department = new Department();
department.setId(UUID.randomUUID().toString().replaceAll("-", ""));
department.setName("测试部门~");
department.setTel("123456789");
departmentMapper.save(department);
// 提交事务才会生效
sqlSession.commit();
sqlSession.close();
然后我们可以运行 main
方法测试下效果了:
[main] DEBUG DepartmentAnnotationMapper.save - ==> Preparing: insert into tbl_department (id, name, tel) values (?, ?, ?)
[main] DEBUG DepartmentAnnotationMapper.save - ==> Parameters: 11c8cdec37e041cf8476c86d46a42dd3(String), 测试部门~(String), 123456789(String)
[main] DEBUG DepartmentAnnotationMapper.save - <== Updates: 1
insert 语句成功执行,数据库中也成功插入了这条数据:
2.1.2 需要返回主键
与之前第 8 章里的一样,insert 数据时可能数据库表的主键使用自增属性,那么在 insert 完成后需要把主键回填到实体类对象中。MyBatis 注解中专门有一个 @Options
属性,可以类比于所有 statement 型标签中都会使用到的大部分属性( useCache
、flushCache
、timeout
等)。
所以按照这样来讲,我们可以定义一个新的 statement ,并配置一下 useGeneratedKeys
和 keyProperty
,这样就跟 mapper.xml 中写的差不多了:
@Insert("insert into tbl_dept2 (name, tel) values (#{name}, #{tel})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int saveUseGeneratedKeys(Department department);
注意配置的是 keyProperty
哦,不要错误的配置成 keyColumn
,这样就没办法回填 id 了。
OK ,同样的我们来快速的写一下测试代码:
Department department = new Department();
department.setName("测试部门~");
department.setTel("123456789");
departmentMapper.saveUseGeneratedKeys(department);
System.out.println(department);
// 提交事务才会生效
sqlSession.commit();
sqlSession.close();
运行测试代码,观察控制台中打印的 department :
[main] DEBUG saveUseGeneratedKeys - ==> Preparing: insert into tbl_dept2 (name, tel) values (?, ?)
[main] DEBUG saveUseGeneratedKeys - ==> Parameters: 测试部门~(String), 123456789(String)
[main] DEBUG saveUseGeneratedKeys - <== Updates: 1
Department{id='7', name='测试部门~', tel='123456789'}
id 已经成功回填了,说明一切成功。
2.2 @Update和@Delete
对于 @Update
和 @Delete
来讲,就没那么多道道了,毕竟它们都没有 @Result
这样的注解配合,直接拿来用就可以:
@Update("update tbl_department set name = #{name} where id = #{id}")
int updateById(Department department);
@Delete("delete from tbl_department where id = #{id}")
int deleteById(String id);
然后我们可以写一点测试代码来看看效果:
Department department = departmentMapper.findById("11c8cdec37e041cf8476c86d46a42dd3");
department.setName("测测试试");
departmentMapper.updateById(department);
departmentMapper.deleteById("11c8cdec37e041cf8476c86d46a42dd3");
sqlSession.commit();
sqlSession.close();
最终的结果肯定是 id
为 11c8cdec37e041cf8476c86d46a42dd3
的部门被删掉了,我们只需要观察一下控制台,SQL 正常打印了就 OK 。
[main] DEBUG Mapper.updateById - ==> Preparing: update tbl_department set name = ? where id = ?
[main] DEBUG Mapper.updateById - ==> Parameters: 测测试试(String), 11c8cdec37e041cf8476c86d46a42dd3(String)
[main] DEBUG Mapper.updateById - <== Updates: 1
[main] DEBUG Mapper.deleteById - ==> Preparing: delete from tbl_department where id = ?
[main] DEBUG Mapper.deleteById - ==> Parameters: 11c8cdec37e041cf8476c86d46a42dd3(String)
[main] DEBUG Mapper.deleteById - <== Updates: 1
其余的也没什么好聊的,注解的使用还是非常简单的。
3. @Options
本章的最后,我们来提一个上面提到过的 @Options
注解,它是为了补充 @Select
、@Insert
、@Update
、@Delete
注解中的相同的属性配置,我们之前编写 mapper.xml 中提到过,这些 statement 型的标签都有不少属性(诸如 useCache
、flushCache
、timeout
),下面小册把属性罗列出来,小伙伴看一下是不是都很熟悉:
boolean useCache() default true;
FlushCachePolicy flushCache() default FlushCachePolicy.DEFAULT;
ResultSetType resultSetType() default ResultSetType.DEFAULT;
StatementType statementType() default StatementType.PREPARED;
int fetchSize() default -1;
int timeout() default -1;
boolean useGeneratedKeys() default false;
String keyProperty() default "";
String keyColumn() default "";
String resultSets() default "";
String databaseId() default "";
可以看得出来,这里面的属性都可以在 mapper.xml 中的那几个 statement 型标签的属性中找到吧!