Mybatis 简化JDBC 的操作,SQL 与代码分离,提高维护性。
1. 环境搭建
Maven 项目,mysql 数据库。
1.1 导入依赖
pom.xml
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
1.2 创建Dao 接口及实体类
AccountMapper
public interface AccountMapper {
List<Account> getAccountList();
}
Account
public class Account {
private int id;
private String name;
private BigDecimal balance;
// 构造器及SetGet方法略
...
}
1.3 创建映射 mapper
相当于Dao 接口的实现类,写Sql 语句的地方。
src\main\java\com\chengyu\dao\AccountMapper.xml
<?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">
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" resultType="com.chengyu.pojo.Account">
select * from book.account
</select>
</mapper>
1.4 配置Mybatis 核心配置文件
配置文件名称任意。
src\main\resources\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>
<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/book?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="tiger"/>
</dataSource>
</environment>
</environments>
<!--注册 mapper-->
<mappers>
<mapper resource="com/chengyu/dao/AccountMapper.xml"/>
</mappers>
</configuration>
1.5 创建MybatisUtils 类
创建Modul 后,新建MybatisUtils 类。
src\main\java\com\chengyu\utils\MybatisUtils.java
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
try{
// 1.获取sqlSesionFactory 对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch (IOException e){
e.printStackTrace();
}
}
// 获取 SqlSession 实例,可以执行已经映射的sql,类似于statement
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
1.6 补充
Mybatis 核心配置文件中对mapper 的加载路径是target/class 下,默认src 下的配置文件是不被加载的,需要添加配置信息,使得映射文件(AccountMapper.xml)一同加载到编译文件中。
项目的 pom.xml(不是Module)中添加如下配置,并右键Maven ⇒ Reimport 一下。
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
重新编译后会发现,target/class 的对应路径下 AccountMapper.xml 文件被加载了。
1.7 测试
public class AccountMapperTest {
@Test
public void test(){
// 获得 SqlSession
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 执行 Sql
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
List<Account> accountList = mapper.getAccountList();
accountList.forEach(System.out::println);
sqlSession.close();
}
}
2. CRUD
2.1 查询
2.2.1 查询多条记录
接口
public interface AccountMapper {
List<Account> getAccountList();
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" resultType="com.chengyu.pojo.Account">
select * from book.account
</select>
</mapper>
测试
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
List<Account> accountList = mapper.getAccountList();
accountList.forEach(System.out::println);
sqlSession.close();
}
2.2.2 查询一条记录
接口
public interface AccountMapper {
Account getAccountById(int id);
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountById" parameterType="int" resultType="com.chengyu.pojo.Account">
select * from book.account where id = #{id}
</select>
</mapper>
测试
@Test
public void testAccountById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Account account = mapper.getAccountById(1001);
System.out.println(account);
sqlSession.close();
}
2.2.3 模糊查询
接口
public interface AccountMapper {
List<Account> getAccountListLike(String value);
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountListLike" resultType="com.chengyu.pojo.Account">
select * from book.account where name like "%"#{value}"%"
</select>
</mapper>
测试
@Test
public void testGetAccountListLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
List<Account> countList = mapper.getAccountListLike("cheng");
countList.forEach(System.out::println);
sqlSession.close();
}
补充:通配符【%】也可以写在Java 代码中,mapper.getAccountListLike("%cheng%");
2.2 插入
2.2.1 实体类作为插入参数
接口
public interface AccountMapper {
int addAccount(Account acc);
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<insert id="addAccount" parameterType="com.chengyu.pojo.Account">
insert into book.account (id,name,balance) value (#{id},#{name},#{balance})
</insert>
</mapper>
测试
@Test
public void testAddAccount(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
int count = mapper.addAccount(new Account("chengyu", new BigDecimal("500")));
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
2.2.2 Map 作为插入参数
接口
public interface AccountMapper {
int addAccount2(Map<String,Object> map);
}
映射:
#{a} 中的名称不必必须遵循实体类的属性,任意。
<mapper namespace="com.chengyu.dao.AccountMapper">
<insert id="addAccount2" parameterType="map">
insert into book.account (name) value (#{a})
</insert>
</mapper>
测试
@Test
public void testAddAccount2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("a","chengcheng");
int count = mapper.addAccount2(map);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
2.3 更新
2.3.1 实体类作为更新参数
接口
public interface AccountMapper {
int updateAccount(Account acc);
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<update id="updateAccount" parameterType="com.chengyu.pojo.Account">
update book.account set name = #{name},balance = #{balance} where id = #{id}
</update>
</mapper>
测试
@Test
public void testUpdateAccount(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
int count = mapper.updateAccount(new Account(1013,"chenglong",new BigDecimal(1500)));
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
2.3.2 Map 作为更新参数
接口
public interface AccountMapper {
int updateAccount2(Map<String,Object> map);
}
映射
#{b} 中的名称不必必须遵循实体类的属性,任意
<mapper namespace="com.chengyu.dao.AccountMapper">
<update id="updateAccount2" parameterType="map">
update book.account set name = #{b} where id = #{id}
</update>
</mapper>
测试
@Test
public void testUpdateAccount2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("id",1017);
map.put("b","chengcheng");
int count = mapper.updateAccount2(map);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
2.4 删除
接口
public interface AccountMapper {
int deleteAccount(int id);
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<delete id="deleteAccount" parameterType="int">
delete from book.account where id = #{id}
</delete>
</mapper>
测试
@Test
public void testDeleteAccount(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
int count = mapper.deleteAccount(1013);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
2.5 补充(占位符)
#{} 和 ${}
都可以作为占位符使用,区别如同PreparedStatement 和Statement。
即 ${} 可能存在SQL 注入问题,不推荐使用。
3. 配置解析
3.1 默认事物配置
Mybatis 默认的事物管理器是JDBC,默认使用连接池(POOLED)。
3.2 数据库连接优化
优化前:
mybatis-config.xml
<!-- 核心配置文件 -->
<configuration>
<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/book?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="tiger"/>
</dataSource>
</environment>
</environments>
</configuration>
优化后:
新建resources/db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/book?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=tiger
resources/mybatis-config.xml
<configuration>
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>
3.3 实体类别名
优化前:
AccountMapper.xml
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" resultType="com.chengyu.pojo.Account">
select * from book.account
</select>
</mapper>
3.3.1 指定实体类别名设置
优化后:
resources/mybatis-config.xml
<configuration>
<typeAliases>
<typeAlias type="com.chengyu.pojo.Account" alias="Account"/>
</typeAliases>
</configuration>
AccountMapper.xml
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" resultType="Account">
select * from book.account
</select>
</mapper>
3.3.2 扫描包
包下的实体类,默认首字母小写,即可当作别名使用。可以通过注解起别名。(如3.3.3)
优化后:
resources/mybatis-config.xml
<configuration>
<typeAliases>
<package name="com.chengyu.pojo"/>
</typeAliases>
</configuration>
AccountMapper.xml
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" resultType="account">
select * from book.account
</select>
</mapper>
3.3.3 注解别名
注解别名需要与扫描包一同使用。
优化后:
@Alias("acc")
public class Account {
private int id;
private String name;
private BigDecimal balance;
...
}
AccountMapper.xml
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" resultType="acc">
select * from book.account
</select>
</mapper>
3.4 设定信息
settings 中有大量设置信息,如日志、驼峰映射等。
mybatis-config.xml
<settings>
<setting name="" value=""/>
</settings>
4. 作用域
MybatisUtils:
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
try{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch (IOException e){
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
4.1 SqlSessionFactoryBuilder
作用域:局部变量
用于创建会话工厂 SqlSessionFactory,不需要使用单例管理。
一旦创建了会话工厂之后,就不再需要它了。
4.2 SqlSessionFactory
作用域:全局作用域
用于创建 SqlSession,使用单例模式管理SqlSessionFactory 。
工厂一旦创建,使用一个实例。
相当于数据库连接池。
4.3 SqlSession
作用域:局部变量
是一个面向用户(程序员)的接口,提供了很多操作数据库的方法。如selectOne、selectList等;
是线程不安全的,应该应用在方法体,定义成局部变量使用;
用完之后需要关闭,否则资源被占用。
@Test
public void testAccountById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Account account = mapper.getAccountById(1001);
System.out.println(account);
sqlSession.close();
}
4.4 AccountMapper
作用域:mapper.xml
每一个Mapper 就代表一个具体的业务。
5. 日志
如果SQL 执行出现异常,可以通过日志进行排错。
有效值:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
5.1 STDOUT_LOGGING
核心配置文件:
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
...
</configuration>
5.2 LOG4J
核心配置文件:
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
...
</configuration>
导包:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
配置文件:
log4j.properties
# 将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
# 控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
# 文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/chengyu.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
# 日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
输出:
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 626742236.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[com.chengyu.dao.AccountMapper.getAccountById]-==> Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1001(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<== Total: 1
Account{id=1001, name='null', balance=700.00}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 626742236 to pool.
类中自定义输出内容:
public class AccountMapperTest {
static Logger logger = Logger.getLogger(AccountMapperTest.class);
@Test
public void test(){
logger.info("====start===");
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
List<Account> accountList = mapper.getAccountList();
accountList.forEach(System.out::println);
sqlSession.close();
logger.info("====end===");
}
}
6. 分页
避免一次性加载大量数据,影响用户体验度。
6.1 limit 分页
接口
public interface AccountMapper {
List<Account> getAccountListLimit(Map<String,Integer> map);
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountListLimit" parameterType="map" resultType="Account">
select id,name as username,balance from book.account limit #{startIndex},#{endIndex}
</select>
</mapper>
测试
@Test
public void testGetAccountListLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map<String,Integer> map = new HashMap<>();
map.put("startIndex",0);
map.put("endIndex",2);
List<Account> accountList = mapper.getAccountListLimit(map);
accountList.forEach(System.out::println);
sqlSession.close();
}
6.2 RowBounds 分页
(不推荐)
接口
public interface AccountMapper {
List<Account> getAccountListRowBounds();
}
映射
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountListRowBounds" resultType="Account">
select id,name as username,balance from book.account
</select>
</mapper>
测试
@Test
public void testGetAccountListRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(0, 2);
List<Account> accountList = sqlSession.selectList("com.chengyu.dao.AccountMapper.getAccountListRowBounds",null,rowBounds);
accountList.forEach(System.out::println);
sqlSession.close();
}
6.3 分页插件
MyBatis 分页插件 PageHelper
按照操作步骤即可添加使用。
7. 注解开发
简单的Sql 语句可以使用注解形式编写,但复杂语句建议使用XML 的形式。
本质:反射机制
底层:动态代理
工具类:
openSession(true) 设置为自动提交。
public class MybatisUtils {
...
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
核心配置文件:
1)扫描AccountMapper 接口,可以删除Mapper.xml 文件。
<mappers>
<mapper class="com.chengyu.dao.AccountMapper"/>
</mappers>
2)原始的扫描Mapper.xml 文件,同样没问题。
因为在AccountMapper.xml 中引入了AccountMapper 接口,所以扫描到Mapper.xml 也可以使用注解。
<mappers>
<mapper resource="com/chengyu/dao/AccountMapper.xml"/>
</mappers>
7.1 查询多条
接口
public interface AccountMapper {
@Select("select * from account")
List<Account> getAccountList();
}
测试
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
List<Account> accountList = mapper.getAccountList();
accountList.forEach(System.out::println);
sqlSession.close();
}
7.2 查询一条
接口
public interface AccountMapper {
@Select("select * from account where id = #{id}")
Account getAccountById(@Param("id") int id);
}
测试
@Test
public void testAccountById(){
// 获得 SqlSession
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 执行 Sql
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Account account = mapper.getAccountById(1001);
System.out.println(account);
sqlSession.close();
}
7.3 添加
接口
public interface AccountMapper {
@Insert("insert into account (name,balance) values (#{name},#{balance})")
int addAccount(Account acc);
}
测试
@Test
public void testAddAccount(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
int count = mapper.addAccount(new Account("chengyu", new BigDecimal("500")));
System.out.println(count);
sqlSession.close();
}
7.4 更新
接口
public interface AccountMapper {
@Update("update account set name=#{name},balance=#{balance} where id=#{id}")
int updateAccount(Account acc);
}
测试
@Test
public void testUpdateAccount(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
int count = mapper.updateAccount(new Account(1020,"chenglong",new BigDecimal(1500)));
System.out.println(count);
sqlSession.close();
}
7.5 删除
接口
public interface AccountMapper {
@Delete("delete from account where id=#{id}")
int deleteAccount(@Param("id") int id);
}
测试
@Test
public void testDeleteAccount(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
int count = mapper.deleteAccount(1021);
System.out.println(count);
sqlSession.close();
}
7.6 补充(@Param)
@Param("")
基本类型或String 类型的参数需要使用;
如果只有一个时可以忽略;
SQL 中引用过的就是这里设置的属性名;
8. 高级映射
8.1 一对一 resultType
<select id="getAccountList" resultType="com.chengyu.pojo.Account">
select id,name,balance from book.account
</select>
8.2 一对一 resultMap
解决实体类属性名和数据库字段名不一致的问题。查询该字段返回null。
DB :name
POJO : username
<!--结果集映射-->
<resultMap id="AccMap" type="com.chengyu.pojo.Account">
<result column="name" property="username"/>
</resultMap>
<select id="getAccountList" resultMap="AccMap">
select id,name,balance from book.account
</select>
补充:Sql 中对字段重命名通用可以解决该问题。
8.3 多对一 resultMap
一个老师对应多个学生,学生实体类中设置老师类属性。
8.3.1 查询嵌套处理
<mapper namespace="com.chengyu.dao.StudentMapper">
<select id="getStudentList" resultMap="StudentTeacherMap">
select * from student s,teacher t where s.tid = t.id;
</select>
<resultMap id="StudentTeacherMap" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂属性 对象:association,集合:collection-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacherList"/>
</resultMap>
<select id="getTeacherList" resultType="Teacher">
select * from teacher where id = #{id}
</select>
</mapper>
8.3.2 结果嵌套处理
<mapper namespace="com.chengyu.dao.StudentMapper">
<select id="getStudentList2" resultMap="StudentTeacherMap2">
select s.id,s.name,t.name tname from student s,teacher t where s.tid = t.id;
</select>
<resultMap id="StudentTeacherMap2" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂属性 对象:association,集合:collection-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
</mapper>
public class Student {
private int id;
private String name;
private Teacher teacher;
...
}
8.4 一对多 resultMap
一个老师对应多个学生,老师实体类中设置List<学生类实体>。
<mapper namespace="com.chengyu.dao.TeacherMapper">
<select id="getStudentListById" resultMap="TeacherStudent">
select t.id,t.name,s.id sid,s.name sname,s.tid
from teacher t,student s
where t.id = s.tid and t.id = #{id}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--集合中的泛型,使用ofType-->
<!--studentList:Teacher 实体类中的Student 变量-->
<collection property="studentList" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
</mapper>
public class Teacher {
private int id;
private String name;
private List<Student> studentList;
...
}
9. 动态 SQL
根据条件对SQL 语句进行灵活的拼接和组装。
9.1 IF
接口
public interface AccountMapper {
List<Account> getAccountList(Map map);
}
mapper.xml
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" parameterType="map" resultType="Account">
select * from account
<where>
<if test="name != null">
and name = #{name}
</if>
<if test="balance != null">
and balance = #{balance}
</if>
</where>
</select>
</mapper>
补充:where 标签自动去除条件中的第一个 and。
测试类
@Test
public void getAccountListTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map map = new HashMap();
map.put("name","chengyu");
//map.put("balance",new BigDecimal("1500"));
List<Account> accountList = mapper.getAccountList(map);
accountList.forEach(System.out::println);
sqlSession.close();
}
9.2 Choose /When /Otherwise
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountList" parameterType="map" resultType="Account">
select * from account
<where>
<choose>
<when test="name != null">
and name = #{name}
</when>
<when test="balance != null">
and balance = #{balance}
</when>
<otherwise>
id = 1001
</otherwise>
</choose>
</where>
</select>
</mapper>
9.3 Set
会自动删除无关的逗号。
<mapper namespace="com.chengyu.dao.AccountMapper">
<update id="updateAccount" parameterType="map">
update account
<set>
<if test="name != null">
name = #{name},
</if>
<if test="balance != null">
balance = #{balance},
</if>
</set>
<where>
and id = #{id}
</where>
</update>
</mapper>
9.4 SQL 片段
共同部分的SQL 语句可以提取出来。
include 部分
<mapper namespace="com.chengyu.dao.AccountMapper">
<update id="updateAccount" parameterType="map">
update account
<set>
<if test="name != null">
name = #{name},
</if>
<if test="balance != null">
balance = #{balance},
</if>
</set>
<where>
<include refid="query_account_where"></include>
</where>
</update>
<sql id="query_account_where">
and id = #{id}
</sql>
</mapper>
9.5 Foreach
常用与Sql 语句的 in 中。
接口
public interface AccountMapper {
List<Account> getAccountListByIds(Map map);
}
mapper.xml
<mapper namespace="com.chengyu.dao.AccountMapper">
<select id="getAccountListByIds" parameterType="map" resultType="Account">
select * from account
<where>
<foreach item="id" collection="ids" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
</mapper>
测试
public void getAccountListByIdsTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Map map = new HashMap();
List<Integer> list = new ArrayList<>();
list.add(1001);
list.add(1002);
map.put("ids",list);
List<Account> accountList = mapper.getAccountListByIds(map);
accountList.forEach(System.out::println);
sqlSession.close();
}
Preparing: select * from account WHERE ( id = ? or id = ? )
Parameters: 1001(Integer), 1002(Integer)
10. 缓存
由于连接数据库比较耗资源,将查询结果暂时存放到一个可以直接取到的地方(内存)。这些暂时放到内存中的数据称为缓存。
再次查询相同数据时,就不用走数据库,直接走缓存即可。
经常查询、不经常改变的数据,可以使用缓存。
10.1 一级缓存
默认开启,SqlSession 级别的缓存,也称本地缓存。
在一次连接开始到连接结束之间有效。
原理:
第一次发起查询用户Id=1001 的用户信息,先去缓存中找是否有 Id=1001 的用户信息,如果没有,从数据库查询,得到信息后,存入一级缓存中;
第二次发起查询Id=1001 的用户信息,到缓存中找到,则取出用户信息。
查询不同内容,不走缓存;
如果SqlSession 去执行增删改操作,会清空一级缓存,这样做的目的是为了让缓存中的数据最新化;
如果执行两次service 调用查询相同的用户信息,不走一级缓存,因为session 方法结束,SqlSession 就关闭,一级缓存就清空了。
手动清理缓存,sqlSession.clearCache();
@Test
public void testAccountById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Account account = mapper.getAccountById(1001);
System.out.println(account);
System.out.println("================================");
Account account2 = mapper.getAccountById(1001);
System.out.println(account2);
sqlSession.close();
}
sqlSession 进行了两次查询,因为利用了缓存信息,查询语句只执行一次。
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 626742236.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[com.chengyu.dao.AccountMapper.getAccountById]-==> Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1001(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<== Total: 1
Account{id=1001, name='null', balance=700.00}
================================
Account{id=1001, name='null', balance=700.00}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 626742236 to pool.
public void testAccountById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
Account account = mapper.getAccountById(1001);
System.out.println(account);
System.out.println("================================");
Account account2 = mapper.getAccountById(1002);
System.out.println(account2);
sqlSession.close();
}
sqlSession 查询两次,但条件不同,没有缓存信息,查询语句执行了两次。
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 626742236.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[com.chengyu.dao.AccountMapper.getAccountById]-==> Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1001(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<== Total: 1
Account{id=1001, name='null', balance=700.00}
================================
[com.chengyu.dao.AccountMapper.getAccountById]-==> Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1002(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<== Total: 1
Account{id=1002, name='null', balance=1500.00}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 626742236 to pool.
10.2 二级缓存
需要手动缓存,基于namespace 级别的缓存(一个mapper),通过实现Cache 接口来自定义二级缓存。
手动开启方式:
① 核心配置文件
<settings>
<setting name="logImpl" value="LOG4J"/>
<!--显示的开启二级缓存(默认就是开启)-->
<setting name="cacheEnabled" value="true"/>
</settings>
② 在mapper.xml 中添加 标记
<mapper namespace="com.chengyu.dao.AccountMapper">
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
</mapper>
原理:
SqlSession1 去查询用户Id=1001 的用户信息,查询到的用户信息后,先将信息储存到一级缓存中,当SqlSession1 关闭后用户信息存储到二级缓存中(转存);
SqlSession2 去查询 Id=1001 ,先到缓存中查找;
如果同一个mapper.xml 中执行了增删改操作,将会清空该 mapper 下的二级缓存;
注意:
使用二级缓存,需要将实体类序列化!
10.3 自定义缓存
可以自定义,也可以整合第三方缓存框架,如Ehcache。