1. MyBatis简介
- MyBatis提供了数据库查询的自动对象绑定功能;SQL写在XML中,便于统一管理和优化,解除了SQL与代码的耦合;执行效率比Hibernate快;对于复杂SQL,使用MyBatis会更加灵活。但关联的表或字段较多时,SQL工作量很大;而且SQL依赖于数据库,导致数据库移植性较差。
- Hibernate是全自动,Hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成SQL;对于单表的CRUD,使用Hibernate开发更加高效。但学习和精通的门槛较高,而且由于是自动生成SQL,因此无法直接维护SQL。
- MyBatis官网:https://mybatis.org/mybatis-3/
- MyBatis作用:
- 减少JDBC的重复代码
- 使数据库环境信息的硬编码和执行的SQL语句的硬编码解耦
- MyBatis分页插件PageHelper网址:https://pagehelper.github.io/
2. MyBatis基本使用
以查询user表中所有user为例
2.1 创建Maven工程,添加坐标
<dependencies>
<!--添加坐标-->
<!--junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--lombok的依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
</dependencies>
2.2 创建POJO
-- 创建user表
CREATE TABLE user(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用户名",
password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密码",
balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余额"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用户表";
-- 添加用户
INSERT INTO user VALUES
(NULL, 'user1', 'password1', 1000),
(NULL, 'user2', 'password2', 2000),
(NULL, 'user3', 'password3', 3000);
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private Double balance;
}
2.3 创建PojoDao接口
原则:MyBatis的所有PojoDao接口,方法参数只设置一个;
由于是全类名+方法名的保存和寻找策略,因此PojoDao接口里的方法不能重载
PojoDao接口的工作原理是JDK动态代理
public interface UserDao {
void addUser(User user);
void deleteById(Integer id);
void updateUser(User user);
List<User> findAll();
}
2.4 创建外部jdbc配置文件
在resources下创建jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db01?useSSL=false
jdbc.username=root
jdbc.password=123456
2.5 创建MyBatis核心配置文件*
- 存储在resources根路径下
- 文件名随意(如:SqlMapConfig.xml)
- 需要引入约束
核心配置文件的顺序:
properties:引入外部properties文件
settings:全局配置参数
-
typeAliases:给类型设置别名
核心配置文件:typeAliases定义单个别名 <typeAliases> <typeAlias type="com.liu2m.pojo.User" alias="User"></typeAlias> </typeAliases> 核心配置文件:typeAliases批量定义别名 <typeAliases> <package name="com.liu2m.pojo"/> </typeAliases> 修改PojoDao.xml: <select id="findAll" resultType="User"> select * from user </select>
typeHandlers:类型处理器
objectFactory:对象工厂
objectWrapperFactory
reflectorFactory
plugins:插件
environments:环境配置
databaseIdProvider:数据库厂商标识
mappers:映射器
<?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>
<!--引入外部properties文件-->
<properties resource="jdbc.properties"></properties>
<!--给类型设置别名-->
<typeAliases>
<package name="com.liu2m.pojo"/>
</typeAliases>
<!--配置连接数据库的环境
default:指定使用哪一个环境-->
<environments default="dev">
<!--一个连接数据库的环境
id:环境变量名-->
<environment id="dev">
<!--配置事务管理者
MyBatis事务使用的是jdbc-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池
UNPOOLED:不使用连接池
POOLED:使用mybatis内置的连接池-->
<dataSource type="POOLED">
<!--value值可以直接写,也可以通过${}设置
${}中写properties引入的文件中的属性名-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db01?useSSL=false&characterEncoding=UTF8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--加载所有的映射文件-->
<mappers>
<!--引入一个映射文件-->
<!--方式1:通过resource属性引入映射文件的路径-->
<!--<mapper resource="com/liu2m/dao/UserDao.xml"/>-->
<!--方式2:扫描接口-->
<!--配置单个接口-->
<!--<mapper class="com.liu2m.dao.UserDao"></mapper>-->
<!--批量配置-->
<package name="com.liu2m.dao"/>
</mappers>
</configuration>
2.6 创建SQL映射文件*
- 存储在resources里面
- 全路径和文件名要和相应的PojoDao接口的相同(在resources里创建多级文件夹时用/分隔,而不是.)
- 需要引入约束
根标签mapper的namespace属性的值为对应的PojoDao接口的全限定名
根标签mapper下的每一个子标签对应PojoDao接口中的一个方法:
- 查询方法对应select标签
- 添加方法对应insert标签
- 删除方法对应delete标签
- 修改方法对应update标签
子标签的id对应方法的名字
子标签的parameterType对应方法的形参类型
子标签的resultType(只有select标签有)对应方法的返回值类型:
- 如果返回值类型是简单类型,则直接写对应的Java类型
- 如果返回值类型是POJO、Map,则直接写POJO的全限定名
- 如果返回值类型是Collection,则resultType是集合中元素的类型
- 结果集的字段名和POJO的属性名能够对应时,resultType才可以自动映射
使用参数中的数据:
- 如果参数为pojo、map,则通过#{属性名或Map的key}来引入对应的值
- 如果参数为基本类型数据,则通过#{任意字符串}来引入对应的值
- 如果参数为pojo包含pojo,如ResultBean中包含User,则使用#{user.username}
resultMap标签:解决查询出来的结果的列名和JavaBean属性不一致的情况
- id属性:映射规则的唯一id
- type属性:将SQL查询出来的结果手动映射到哪个类
<?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.liu2m.dao.UserDao">
<resultMap id="findAllMap" type="User">
<!--id:查询结果集的唯一标识;如果唯一约束是多个字段段,则定义多个id-->
<id property="id" column="u_id"></id>
<!--
property:指定的POJO类的属性
column:sql查询出来的字段名-->
<result property="username" column="u_username"></result>
<result property="password" column="u_password"></result>
<result property="balance" column="u_balance"></result>
</resultMap>
<select id="findAll" resultMap="findAllMap">
select id as u_id, username as u_username, password as u_password, balance as u_balance from user
</select>
<!--selectKey可以获取主键,存储在对应的POJO对象中
keyColumn:要查询的字段名
keyProperty:将查询到的结果赋值到哪个属性
resultType:查询到的结果的类型
order:在执行添加之前还是之后执行selectKey里的SQL-->
<insert id="addUser" parameterType="com.liu2m.pojo.User">
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into user values (null, #{username}, #{password}, #{balance})
</insert>
<update id="updateUser" parameterType="com.liu2m.pojo.User">
update user set username = #{username}, password = #{password}, balance = #{balance}
</update>
<delete id="deleteById" parameterType="java.lang.Integer">
delete from user where id = #{id}
</delete>
</mapper>
2.7 单元测试
public class UserDaoTest {
InputStream is;
SqlSession sqlSession;
UserDao userDao;
@Before
public void init() throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 读取核心配置文件,转换成字节输入流
is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 创建SqlSessionFactory对象
SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(is);
// 4. 创建SqlSession对象
sqlSession = sessionFactory.openSession();
// 5. 创建UserDao的代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
@Test
public void testFindAll() {
// 6. 调用需要执行的方法
List<User> userList = userDao.findAll();
for (User user : userList) {
System.out.println(user);
}
}
@Test
public void testAddUser() {
User user = new User(0, "user4", "654321", 4000.0);
userDao.addUser(user);
System.out.println(user.getId());
}
@After
public void destroy() throws IOException {
// 在CUD操作中,需要通过sqlSession.commit()方法来执行提交操作
sqlSession.commit();
// 7. 释放资源
sqlSession.close();
is.close();
}
}
2.8 #{}与${}的区别*
- 传参的区别:
-
#{}
先用?占位,然后再设置参数,可以防止SQL注入 - ${}直接拼接SQL语句
-
-
#{}
不能写在引号里,而${}必须要写在引号里 - pojo、map类型的参数,#{}和${}括号里都是写属性名或Map的key
- 基本类型、字符串类型的参数,#{}和${}括号里可以写任意字符串
- 模糊查询:
- "%"#{username}"%"
- concat("%", #{username}, "%")
- "%${value}%"
2.9 自定义SqlSessionFactory工具类
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class SqlSessionFactoryUtils {
private static InputStream is;
private static SqlSessionFactory sessionFactory;
static {
try {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 读取核心配置文件,转换成字节输入流
is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 创建SqlSessionFactory对象
sessionFactory = sqlSessionFactoryBuilder.build(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取一个SqlSession
*/
public static SqlSession openSession() {
SqlSession sqlSession = null;
// 4. 创建SqlSession对象
sqlSession = sessionFactory.openSession();
return sqlSession;
}
/**
* 提交事务并关闭资源
*/
public static void commitAndClose(SqlSession sqlSession) {
sqlSession.commit();
sqlSession.close();
}
/**
* 回滚事务并关闭资源
*/
public static void rollbackAndClose(SqlSession sqlSession) {
sqlSession.rollback();
sqlSession.close();
}
}
3. MyBatis日志
-
导入坐标:
<!--使用log4j日志打印框架打印mybatis日志--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.6</version> </dependency>
-
在resources下创建log4j.properties文件:
#日志级别:ERROR > WARN > INFO > DEBUG log4j.rootLogger=DEBUG,stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%-5p] %t %l %d %rms:%m%n log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=E:\\Logs\\IdeaLogs\\IdeaLogs.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p [%t] {%c}-%m%n
4. Mybatis动态SQL*
动态SQL:SQL语句的主体结构,在编译时无法确定,只有等到程序运行后,在执行过程中才能确定
4.1 if标签
满足test属性的逻辑表达式,则if标签里面的语句有效
多条件查询时,每次查询的条件不确定有哪些,就可以使用if标签
当参数值为int的0时,MyBatis会将参数值其转化为空字符串
select * from user
<!--如果参数balance大于0,则添加balance的条件-->
<if test="balance != null and balance > 0">
where balance = #{balance}
</if>
4.2 where标签
select * from user
<!--where标签的作用:
如果有if条件成立,则会在SQL语句中添加WHERE关键字,并且还会自动去掉第一个条件的and-->
<where>
<if test="balance != null and balance > 0">
and balance = #{balance}
</if>
<if test="password != null and password.length > 6">
and password = #{password}
</if>
</where>
4.3 foreach标签
<!--删除所有在id集合中的行-->
delete from user
<!--
collection:要遍历的对象;如果要遍历的对象是一个集合,就写list
item:遍历出来的每一个元素
index:遍历出来的每一个元素的下标
separator:分隔符
open:在遍历出来的第一个元素之前拼接的字符串
close:在遍历出来的最后一个元素之后拼接的字符串
-->
<foreach open="where id in (" close=")" collection="list" item="id" index="i" separator=", ">
#{id}
</foreach>
4.4 sql标签和include标签
sql标签可以定义公共的sql片段,需要时可以用include标签引入sql片段
<sql id="findAll">
select * from user
</sql>
<select id="findAll" resultType="User" parameterType="int">
<include refid="findAll"></include>
where id = #{id}
</select>
5. Mybatis多表关联查询*
建立数据库环境:
DROP TABLE IF EXISTS user;
-- 创建用户表
CREATE TABLE user(
uid INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用户名",
password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密码",
balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余额"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用户表";
-- 添加用户
INSERT INTO user VALUES
(NULL, 'user1', 'password1', 1000),
(NULL, 'user2', 'password2', 2000),
(NULL, 'user3', 'password3', 3000);
-- 创建账户表
CREATE TABLE account (
aid INT AUTO_INCREMENT PRIMARY KEY,
money DOUBLE,
uid INT,
CONSTRAINT fk_account_uid FOREIGN KEY (uid) REFERENCES user(uid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加账户
INSERT INTO account VALUES
(NULL, 1000, 1),
(NULL, 2000, 1),
(NULL, 1000, 2),
(NULL, 2000, 2),
(NULL, 3000, 3);
-- 创建课程表
CREATE TABLE course(
cid INT PRIMARY KEY AUTO_INCREMENT,
coursename VARCHAR(255),
book VARCHAR(255)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加课程
INSERT INTO course VALUES
(NULL, '面向对象程序设计', 'Java编程思想'),
(NULL, '数据结构与算法', '算法导论'),
(NULL, '设计模式', '大话设计模式');
-- 创建选课表
CREATE TABLE course_selection(
uid INT,
cid INT,
CONSTRAINT fk_course_selection_uid FOREIGN KEY (uid) REFERENCES user(uid),
CONSTRAINT fk_course_selection_cid FOREIGN KEY (cid) REFERENCES course(cid),
PRIMARY KEY (uid, cid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加选课信息
INSERT INTO course_selection VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 1),
(2, 2),
(3, 1);
5.1 一对一
根据账户id查询账户信息,并关联查询对应的用户表信息;由于一个账户只能对应一个用户,因此为一对一查询
在外键所在的POJO中新增一个外键参考的POJO类型的属性
-
创建Account表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; /** * 表示一个Account对应一个User */ private User user; }
-
创建PojoDao接口:
public interface AccountDao { Account findAccountUserByAid(int aid); }
-
创建SQL映射文件:
<?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.liu2m.dao.AccountDao"> <!--使用resultMap自定义映射规则--> <resultMap id="accountUserMap" type="Account"> <id column="aid" property="aid"></id> <result column="money" property="money"></result> <result column="uid" property="uid"></result> <!--使用association标签配置一对一的映射 property:要映射的POJO属性的变量名 javaType:要映射的POJO属性的类型--> <association property="user" javaType="User"> <!--result语法同上面的result--> <result column="uid" property="uid"></result> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> </association> </resultMap> <select id="findAccountUserByAid" parameterType="int" resultMap="accountUserMap"> SELECT * FROM user JOIN account ON user.uid = account.uid WHERE account.aid = #{aid} </select> </mapper>
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account account = accountDao.findAccountUserByAid(1); System.out.println(account); /** * 结果: * Account(aid=1, money=1000.0, uid=1, user=User(id=null, uid=1, username=user1, password=password1, balance=1000.0)) */ }
5.2 一对多
根据用户id查询用户信息,并关联查询对应的所有账户表信息;由于一个用户可能对应多个账户,因此为一对多查询
在外键参考的POJO中新增一个List<外键所在POJO>类型的属性
-
创建User表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一个User对应多个Account */ private List<Account> accountList; }
-
创建PojoDao接口:
public interface UserDao { User findUserAccountByUid(int uid); }
-
创建SQL映射文件:
<?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.liu2m.dao.UserDao"> <!--使用resultMap自定义映射规则--> <resultMap id="userAccountMap" type="User"> <id column="uid" property="uid"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> <!--使用collection标签配置一对多的映射 property:要映射的POJO属性的变量名 ofType:查询出的每一行数据映射成哪一个POJO类型--> <collection property="accountList" ofType="Account"> <!--result语法同上面的result--> <result column="aid" property="aid"></result> <result column="money" property="money"></result> <result column="uid" property="uid"></result> </collection> </resultMap> <select id="findUserAccountByUid" parameterType="int" resultMap="userAccountMap"> SELECT * FROM user JOIN account ON user.uid = account.uid WHERE user.uid = #{uid} </select> </mapper>
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserAccountByUid(1); System.out.println(user); /** * 结果: * User(uid=1, username=user1, password=password1, balance=1000.0, * accountList=[Account(aid=1, money=1000.0, uid=1, user=null), Account(aid=2, money=2000.0, uid=1, user=null)]) */ }
5.3 多对多
根据用户id查询用户所选的所有课程信息,相当于一个用户对应多个课程
多对多关系可以看成是双向的一对多关系
-
创建Course表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class Course implements Serializable { private Integer cid; private String coursename; private String book; }
-
创建User表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一个User对应多个Course */ private List<Course> courseList; }
-
创建PojoDao接口:
public interface UserDao { User findUserCourseByUid(int uid); }
-
创建SQL映射文件:
<?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.liu2m.dao.UserDao"> <!--使用resultMap自定义映射规则--> <resultMap id="userCourseMap" type="User"> <id column="uid" property="uid"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> <collection property="courseList" ofType="Course"> <result column="cid" property="cid"></result> <result column="coursename" property="coursename"></result> <result column="book" property="book"></result> </collection> </resultMap> <select id="findUserCourseByUid" parameterType="int" resultMap="userCourseMap"> SELECT * FROM user JOIN course_selection ON user.uid = course_selection.uid JOIN course ON course.cid = course_selection.cid WHERE user.uid = #{uid}; </select> </mapper>
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserCourseByUid(2); System.out.println(user); /** * 结果: * User(uid=2, username=user2, password=password2, balance=2000.0, * courseList=[Course(cid=1, coursename=面向对象程序设计, book=Java编程思想), Course(cid=2, coursename=数据结构与算法, book=算法导论)]) */ }
6. Mybatis分步查询延迟加载*
- 立即加载:一调用方法就立即发起查询
- 延迟加载(懒加载):需要使用数据时,才发起查询;如果暂时不需要使用数据,则暂时不查询
- 缺点:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
- 优点:先从单表查询,需要时再从关联表去关联查询;由于查询单表要比关联查询多张表速度要快,因此可以提高数据库性能
- 延迟加载的SQL语句类似于嵌套子查询,分步骤查询
-
全局延迟加载:在MyBatis核心配置文件中配置
<settings> <!--lazyLoadingEnabled=true:所有关联对象都会延迟加载 特定关联关系中可设置fetchType属性来覆盖lazyLoadingEnabled--> <setting name="lazyLoadingEnabled" value="true"/> <!--aggressiveLazyLoading=false:每个属性会按需加载 true:对具有懒加载特性对象的任意调用都会加载该对象的所有属性--> <setting name="aggressiveLazyLoading" value="false"/> </settings>
6.1 association标签实现分步查询延迟加载
根据账户id查询账户信息,在需要用户信息时再关联查询对应的用户表信息
-
创建User表和Account表的POJO:
@NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; } @Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; /** * 表示一个Account对应一个User */ private User user; }
-
创建PojoDao接口:
public interface AccountDao { Account findAccountByAid(int aid); } public interface UserDao { User findUserByUid(int uid); }
-
创建SQL映射文件AccountDao.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.liu2m.dao.AccountDao"> <resultMap id="accountUserMap" type="Account"> <id column="aid" property="aid"></id> <result column="money" property="money"></result> <result column="uid" property="uid"></result> <!--使用association标签配置一对一映射的延迟加载 fetchType="lazy":局部延迟加载(配置了这个resultMap的查询会延迟加载) select:调用其他的select标签(方法) column:往调用的select标签(方法)中传入的参数--> <association property="user" javaType="User" fetchType="lazy" select="com.liu2m.dao.UserDao.findUserByUid" column="uid"> </association> </resultMap> <select id="findAccountByAid" parameterType="int" resultMap="accountUserMap"> SELECT * FROM account WHERE aid = #{aid} </select> </mapper>
-
创建SQL映射文件UserDao.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.liu2m.dao.UserDao"> <select id="findUserByUid" parameterType="int" resultType="User"> SELECT * FROM user WHERE uid = #{uid} </select> </mapper>
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account account = accountDao.findAccountByAid(1); System.out.println(account.getMoney()); System.out.println(account.getUser()); }
6.2 collection标签实现分步查询延迟加载
根据用户id查询用户信息,在需要账户信息时再关联查询对应的所有账户表信息
-
创建User表和Account表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一个User对应多个Account */ private List<Account> accountList; } @Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; }
-
创建PojoDao接口:
public interface UserDao { User findUserByUid(int uid); } public interface AccountDao { List<Account> findAccountListByUid(int uid); }
-
创建SQL映射文件UserDao.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.liu2m.dao.UserDao"> <resultMap id="userAccountMap" type="User"> <id column="uid" property="uid"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> <!--使用collection标签配置一对多映射的延迟加载 fetchType="lazy":局部延迟加载(配置了这个resultMap的查询会延迟加载) select:调用其他的select标签(方法) column:往调用的select标签(方法)中传入的参数--> <collection property="accountList" ofType="Account" fetchType="lazy" select="com.liu2m.dao.AccountDao.findAccountListByUid" column="uid"> </collection> </resultMap> <select id="findUserByUid" parameterType="int" resultMap="userAccountMap"> SELECT * FROM user WHERE uid = #{uid} </select> </mapper>
-
创建SQL映射文件AccountDao.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.liu2m.dao.AccountDao"> <select id="findAccountListByUid" resultType="com.liu2m.pojo.Account"> SELECT * FROM account WHERE uid = #{uid} </select> </mapper>
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserByUid(1); System.out.println(user.getUsername()); System.out.println(user.getAccountList()); }
7. MyBatis缓存-
MyBatis缓存类别:
- 一级缓存:是sqlSession对象的缓存,自带的(不需要配置)不可卸载的(不想使用还不行);一级缓存的生命周期与sqlSession一致
- 二级缓存:是SqlSessionFactory的缓存,只要是同一个SqlSessionFactory创建的SqlSession就共享二级缓存的内容,并且可以操作二级缓存。二级缓存如果要使用,需要手动开启(需要配置)
7.1 一级缓存
- 查询时,先去缓存中找;如果缓存中没有,则从数据库查询用户信息,然后将查询到的信息存储到缓存中;如果缓存中有,则直接从缓存中获取信息。
- 清除一级缓存:
- sqlSession.commit(); // commit时清除一级缓存是为了避免数据发生增删改后产生脏读
- sqlSession.close();
- sqlSession.clearCache();
- 执行增删改操作
7.2 二级缓存
-
步骤1:在MyBatis核心配置文件中开启二级缓存(由于默认开启,因此这一步可省略)
<!--cacheEnabled: true:开启二级缓存(默认) false:不开启二级缓存--> <settings> <setting name="cacheEnabled" value="true"/> </settings>
-
步骤2:在要使用二级缓存的SQL映射文件的mapper标签里增加cache标签
<mapper namespace="com.liu2m.dao.UserDao"> <cache></cache> <select id="findAll" resultType="com.liu2m.pojo.User"> select * from user </select> </mapper>
步骤3:要使用二级缓存的POJO类必须实现Serializable接口
- 只有当前的sqlSession.close()时,该sqlSession的数据才会存入二级缓存中
- 执行增删改操作时会清除二级缓存
- select标签属性:
- flushCache默认为false
- useCache默认为true
- insert、delete、update标签属性:
- flushCache默认为true
- 没有useCache属性
8. MyBatis注解-
使用注解代替了SQL映射文件
@SelectKey():代替了selectKey标签
@Insert("SQL"):代替了insert标签
@Delete("SQL"):代替了delete标签
@Update("SQL"):代替了update标签
@Select("SQL"):代替了select标签
@Results(id = "", value = @Result())
或@Results(@Result())
或@Results({@Result(), @Result()})
@Result(column = "列名", property = "属性名", one = @One(select = "指定用来多表查询的sqlmapper"), many = @Many(select = ""))
@Results代替了resultMap标签
@Result代替了result标签和id标签
@One()代替了association标签
@Many()代替了collection标签
8.1 MyBatis注解实现基本CRUD
-
创建PojoDao接口:
public interface UserDao { @SelectKey(keyProperty = "uid", keyColumn = "uid", before = false, statement = "select last_insert_id()", resultType = int.class) @Insert("INSERT INTO user VALUES (NULL, #{username}, #{password}, #{balance})") void addUser(User user); @Delete("delete from user where uid = #{id}") void deleteById(Integer id); @Update("update user set username = #{username}, password = #{password}, balance = #{balance} where uid = #{uid}") void updateUser(User user); @Select("select * from user") List<User> findAll(); }
创建MyBatis核心配置文件
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = new User(2, "user4", "password4", 4000.0); userDao.addUser(user); System.out.println(user.getUid()); userDao.deleteById(3); userDao.updateUser(user); userDao.findAll().stream().forEach(System.out::println); SqlSessionFactoryUtils.commitAndClose(sqlSession); }
8.2 MyBatis注解实现一对一映射及延迟加载
-
创建Account表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; /** * 表示一个Account对应一个User */ private User user; }
-
创建PojoDao接口:
public interface UserDao { @Select("select * from user where uid = #{id}") User findUserByUid(int uid); } public interface AccountDao { @Results(id = "accountUserMap", value = { @Result(column = "uid", property = "uid"), @Result(property = "user", one = @One(select = "com.liu2m.dao.UserDao.findUserByUid", fetchType = FetchType.LAZY), column = "uid") }) @Select("select * from account where aid = #{aid}") Account findAccountByAid(int aid); }
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account account = accountDao.findAccountByAid(1); System.out.println(account.getMoney()); }
8.3 MyBatis注解实现一对多映射及延迟加载
-
创建User表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一个User对应多个Account */ private List<Account> accountList; }
-
创建PojoDao接口:
public interface UserDao { @Results(id = "userAccountMap", value = { @Result(column = "uid", property = "uid", id = true), @Result(property = "accountList", many = @Many(select = "com.liu2m.dao.AccountDao.findAccountListByUid", fetchType = FetchType.LAZY), column = "uid") }) @Select("SELECT * FROM user WHERE uid = #{uid}") User findUserByUid(int uid); } public interface AccountDao { @Select("SELECT * FROM account WHERE uid = #{uid}") List<Account> findAccountListByUid(int uid); }
-
单元测试:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserByUid(1); System.out.println(user); }