目录
- 01Mybatis 延迟加载
- 1.1什么是延时加载?
- 1.2演示案例需求
- 1.3使用 assocation(多对一) 实现延迟加载
- 13.1.1新建一个maven项目(Mybatis),导入坐标
- 1.3.1.2数据库还是用之前的myMybatis数据库
- 1.3.1.3IUserDao接口中内容
- 1.3.1.4 IAccountDao中内容
- 1.3.1.5 Account.java中内容,生成属性的set和get方法,并且重写toString方法(重写时不要加上user属性)
- 1.3.1.6User.java中内容,生成属性的set和get方法,并且重写toString方法(重写时不要加上accounts属性)
- 1.3.1.7IAccountDao.xml内容(重点)
- 1.3.1.8IUserDao.xml中内容
- 1.3.1.9db.properties中内容
- 1.3.1.10 log4j.properties中内容
- 1.3.1.11 SqlMapConfig.xml中内容(重点)
- 1.3.1.12 测试类AccountTest中内容
- 1.4使用 collection(一对多) 实现延迟加载
- 02Mybatis 缓存
- 2.1什么是缓存?
- 2.2为什么使用缓存?
- 2.3什么样的数据可以使用缓存?
- 2.4 Mybatis 中的一级缓存
- 2.4.1验证一级缓存的存在
- 2.4.1.1新建一个maven工程(mybatis)
- 2.4.1.2数据库同样用之前的数据库
- 2.4.1.3IUserDao接口中内容
- 2.4.1.4User.java 中内容,生成属性的set和get方法,并且重写toString方法
- 2.4.1.5IUserDao.xml配置文件内容
- 2.4.1.6 db.properties和log4j.properties中内容和上面的一样
- 2.4.1.7SqlMapConfig.xml中内容和上面的一样
- 2.4.1.8UserTest.java中内容
- 2.4.2验证一级缓存什么情况下消失
- 2.5Mybatis中的二级缓存
01Mybatis 延迟加载
- 有时候我们在加载用户信息时不一定非要加载它的账户信息,这是候就要用到延时加载
1.1什么是延时加载?
- 延迟加载:
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载. - 好处:
先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。 - 坏处:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
1.2演示案例需求
- 1,多对一的演示:查询账户(Account)信息并且关联查询用户(User)信息。先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。这时把对用户(User)信息的按需去查询就是延迟加载。
- 2,一对多的演示:查询账户(User)信息并且关联查询账户(Account)信息,先查询用户(User)信息即可满足要求,当我们需要查询账户(Account)信息时在查询账户(Account)信息,这时把对账户(Account)信息的按需查询就是延时加载。
- 3,resultMap中的 association(多对一)、 collection(一对多)标签具备延迟加载功能。
1.3使用 assocation(多对一) 实现延迟加载
13.1.1新建一个maven项目(Mybatis),导入坐标
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
1.3.1.2数据库还是用之前的myMybatis数据库
1.3.1.3IUserDao接口中内容
public interface IUserDao {
/*根据 id 查询*/
/*账户只能对应一个用户,多对一,不需要集合接收,配置的时候使用association*/
User findUserById(Integer userId);
}
1.3.1.4 IAccountDao中内容
public interface IAccountDao {
/*查询所有账户*/
List<Account> findAll();
}
1.3.1.5 Account.java中内容,生成属性的set和get方法,并且重写toString方法(重写时不要加上user属性)
private Integer id;
private Integer uid;
private Double money;
//多对一关系映射关系:从表实体应该包含一个主表实体的对象引用
private User user;
1.3.1.6User.java中内容,生成属性的set和get方法,并且重写toString方法(重写时不要加上accounts属性)
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
//一对多关系映射:主表实体应该包含从表实体的集合引用
private List<Account> accounts;
1.3.1.7IAccountDao.xml内容(重点)
- 注意resultMap中的association中的配置方式
<?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.aismall.dao.IAccountDao">
<!-- 建立对应关系 -->
<resultMap id="accountUserMap" type="account" >
<id column="id" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 -->
<!--select:查询用户的唯一标识
column : 填写我们要传递给 select 映射的参数-->
<association property="user" javaType="user"
select="com.aismall.dao.IUserDao.findUserById" column="uid">
</association>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="accountUserMap">
select * from account
</select>
</mapper>
1.3.1.8IUserDao.xml中内容
- 通过IAccountDao中association标签,在需要加载时,调用这个findUserById方法,结果被封装到User实体类中
<?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.aismall.dao.IUserDao">
<!-- 根据 id 查询 -->
<select id="findUserById" parameterType="INT" resultType="user">
select * from user where id = #{uid}
</select>
</mapper>
1.3.1.9db.properties中内容
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/myMybatis
jdbc.username=root
jdbc.password=12345678
1.3.1.10 log4j.properties中内容
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
1.3.1.11 SqlMapConfig.xml中内容(重点)
- 注意:setting标签中的内容,开启延时加载,不开启是没有延时加载的。
<?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="db.properties"></properties>
<!--配置参数-->
<settings>
<!--开启Mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
<!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<package name="com.aismall.domain"></package>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置 -->
<mappers>
<package name="com.aismall.dao"></package>
</mappers>
</configuration>
1.3.1.12 测试类AccountTest中内容
- 当我们不需要用户信息时(即:我们不打印用户信息),用户信息就不会被查询。
public class AccountTest {
private InputStream in;
private SqlSession sqlSession;
private IAccountDao accountDao;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession(true);
//4.获取dao的代理对象
accountDao = sqlSession.getMapper(IAccountDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//提交事务
// sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/**
* 测试查询所有
*/
@Test
public void testFindAll() {
//6.执行操作
List<Account> accounts = accountDao.findAll();
for (Account account:accounts) {
System.out.println("---------------");
System.out.println(account);
//当我们不需要输出user中内容时没就不会被加载
// System.out.println(account.getUser());
}
}
}
1.4使用 collection(一对多) 实现延迟加载
我们在刚才的代码中进行改写
1.4.1,在IAccountDao中添加方法
/*根据Id查询用户*/
/*用户对应的账户可能为多个,一对多,使用集合接收,配置的时候使用collection*/
List<Account> findAccountById(Integer accountId);
1.4.2在IUserDao中添加方法
/*查询所有*/
List<User> findAll();
1.4.3在IAccountDao.xml中添加内容(重点)
<!--根据Id查询用户-->
<select id="findAccountById" parameterType="INT" resultType="account">
select * from account where uid=#{id}
</select>
1.4.4在IUserDao.xml中添加内容(重点)
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
<collection property="accounts" ofType="account"
select="com.aismall.dao.IAccountDao.findAccountById" column="id">
</collection>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="userAccountMap">
select * from user
</select>
1.4.5在UserTest中添加内容
public class UserTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession(true);
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//提交事务
// sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/**
* 测试查询所有
*/
@Test
public void testFindAll(){
List<User> users = userDao.findAll();
for(User user : users){
System.out.println("-----每个用户的信息------");
System.out.println(user);
System.out.println(user.getAccounts());
}
}
}
02Mybatis 缓存
- 像大多数的持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数, 从而提高性能。
- Mybatis 中缓存分为一级缓存,二级缓存
2.1什么是缓存?
缓存就是在内存中的临时数据
2.2为什么使用缓存?
减少与数据库的交互次数,提高执行效率
2.3什么样的数据可以使用缓存?
- 经常查询且不经常改变的
- 数据正确与否对最终结果影响不大的(因为缓存中的数据可能与真实数据库中的数据有点出入,如果这个数据意义重大就不能放在缓存中)
2.4 Mybatis 中的一级缓存
- 一级缓存:
它指的是Mybatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中,查询是否有,有的话直接拿出来用。
当SqlSession对象消失时( flush 或 close),mybatis的一级缓存也就消失了。
2.4.1验证一级缓存的存在
2.4.1.1新建一个maven工程(mybatis)
- 坐标使用上面的坐标
2.4.1.2数据库同样用之前的数据库
2.4.1.3IUserDao接口中内容
public interface IUserDao {
/*查询所有*/
List<User> findAll();
/*根据 id 查询*/
User findUserById(Integer userId);
/*更新用户*/
void updateUser(User user);
}
2.4.1.4User.java 中内容,生成属性的set和get方法,并且重写toString方法
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
2.4.1.5IUserDao.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.aismall.dao.IUserDao">
<!--查询所有-->
<select id="findAll" resultType="user">
select * from user
</select>
<!-- 根据 id 查询 -->
<select id="findUserById" parameterType="INT" resultType="user">
select * from user where id = #{uid}
</select>
<!--更新用户信息-->
<update id="updateUser" parameterType="user">
update user set username=#{username},address=#{address} where id=#{id}
</update>
</mapper>
2.4.1.6 db.properties和log4j.properties中内容和上面的一样
2.4.1.7SqlMapConfig.xml中内容和上面的一样
2.4.1.8UserTest.java中内容
- user1=user2说明一级缓存存在
public class UserTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession(true);
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//提交事务
// sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
/*测试一级缓存:不关闭session情况下*/
@Test
public void testFirstLevelCache1(){
User user1=userDao.findUserById(41);
System.out.println(user1);
User user2=userDao.findUserById(41);
System.out.println(user2);
//返回true就证明是同一个对象
System.out.println(user1==user2);
}
}
2.4.2验证一级缓存什么情况下消失
- 一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除, commit()((执行插入、更新、删除)), close()等方法时,就会清空一级缓存。
2.4.2.1在UserTest.java中添加测试方法
- 关闭SqlSession时,看是否存在
/*测试一级缓存:关闭session情况下*/
@Test
public void testFirstLevelCache2(){
User user1=userDao.findUserById(41);
System.out.println(user1);
/*
//第一种方法
//关闭session
sqlSession.close();
//重新获取session
sqlSession= factory.openSession();
userDao=sqlSession.getMapper(IUserDao.class);
*/
//第二种方法
sqlSession.clearCache();
User user2=userDao.findUserById(41);
System.out.println(user2);
//返回false就证明是一级缓存被释放
System.out.println(user1==user2);
}
- 调用update时,看是否存在
/*测试缓存同步*/
@Test
public void testUpdateUser(){
//1.根据id查询用户
User user1=userDao.findUserById(41);
System.out.println(user1);
//2.更新用户信息
user1.setUsername("updateUser");
user1.setAddress("北京");
//3.更新用户
userDao.updateUser(user1);
//4.再次查询
User user2=userDao.findUserById(41);
System.out.println(user2);
//返回false就证明是一级缓存被释放
System.out.println(user1==user2);
}
分析:
- 第一次发起查询 id 为41 的用户信息,先去找缓存中是否有 id 为 41 的用户信息。
- 如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
- 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),将会清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询id 为 41 的用户信息,先去找缓存中是否有 id 为 41 的用户信息,缓存中有,直接从缓存中获取用户信息
2.5Mybatis中的二级缓存
- 二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
- 二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
2.5.1二级缓存的开启与关闭
- 在上面的工程中修改
2.5.1.1第一步:在 SqlMapConfig.xml 文件开启二级缓存
- 因为 cacheEnabled 的默认值为 true,所以这一步可以省略不配置。
- 为 true 代表开启二级缓存;为false 代表不开启二级缓存。
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2.5.1.2 第二步:配置相关的 Mapper 映射文件
<!--开始user支持二级缓存-->
<cache/>
2.5.1.3第三步: 配置 statement 上面的 useCache 属性
- 修改IUserDao.xml
<!--开始user支持二级缓存-->
<cache/>
<!-- 根据 id 查询 -->
<select id="findUserById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
</select>
<select>
标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。- 注意: 针对每次查询都需要最新的数据 sql,要设置成useCache=false,禁用二级缓存。
2.5.2二级缓存测试
- 在Tess文件加下编写测试类SecondLevelCache.java
public class SecondLevelCache {
private InputStream in;
private SqlSessionFactory factory;
@Before//用 于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
}
@After//用于在测试方法执行之后执行
public void destroy()throws Exception{
//提交事务
// sqlSession.commit();
in.close();
}
/*测试二级缓存*/
@Test
public void testSecondLevelCache1(){
//3.获取SqlSession对象
SqlSession sqlSession1=factory.openSession();
//4.获取dao的代理对象
IUserDao userDao1=sqlSession1.getMapper(IUserDao.class);
User user1=userDao1.findUserById(41);
System.out.println(user1);
//5.释放资源
sqlSession1.close();
SqlSession sqlSession2=factory.openSession();
IUserDao userDao2=sqlSession2.getMapper(IUserDao.class);
User user2=userDao2.findUserById(41);
System.out.println(userDao2);
sqlSession2.close();
System.out.println(user1==user2);
}
}
2.5.3二级缓存注意事项
- 当我们在使用二级缓存时,所缓存的类一定要实现java.io.Serializable 接口,这种就可以使用序列化方式来保存对象
- 例如:
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
- 二级缓存中存放的是数据,不是对象,所以每次每次从二级缓存中查数据都会创建一个新的对象把数据封装起来,返回的对象虽然不是一个但是数据是相同的
- 一级缓存中存放的是对象,每次返回的对象都是相同的