Mybatis的延迟加载
比如在查询订单信息,并且关联查询该订单对应的用户信息。
注意两个问题:
1.要是用延迟加载必须使用ResultMap作为输出映射方式
2.Mybatis默认没有延迟加载, 所以需要在全局配置文件中加入<settings>配置
在sqlMapConfig.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>
<!-- 加载数据库配置信息的属性文件, 跟spring整合以后就没了 -->
<properties resource="dbConfig.properties">
<!--<property name="user" value="root1"/>-->
</properties>
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载取消 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 配置自定义类型别名
type: 本来的类型
alias: 别名
<package name=""/>: 批量扫描某一个包, 如果是批量配置别名, 那么
别名就是类名, 并且首字母大写和小写都可以
-->
<typeAliases>
<!-- 配置单个别名 -->
<!--<typeAlias type="com.wtu.entity.User" alias="user"/>-->
<package name="com.wtu.pojo"/>
</typeAliases>
<!-- environments 里面的内容在mybatis和spring整合以后, 就全部没了 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driverClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 通过mapper接口来加载映射文件
1. 映射文件的文件名必须和接口名相同
2. 映射文件必须和Mapper接口处于同一个目录下
3. 这种方式只适合Mapper代理开发模式 -->
<!--<mapper class="com.wtu.mapper.UserMapper"/>-->
<!-- 批量加载映射文件, 自动去扫描某一个包, 将该包下的所有映射文件加载 -->
<package name="com.wtu.mapper"/>
</mappers>
</configuration>
实体类:
public class Orders {
private Integer id;
private Integer user_id;
private Integer number;
private Date createtime;
private String note;
// 封装用户信息
private User user;
// ...
}
映射文件的编写:
<!-- 定义延迟加载用户信息的resultMap -->
<resultMap id="lazyLoadingUser" type="orders">
<id column="id" property="id"/>
<result column="user_id" property="user_id"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 将用户的信息映射到Orders类中user属性
select: 表示延迟加载的时候, 需要去查询哪一条SQL
加入该SQL在其它的映射文件中的时候, 那么需要在前面加上namespace
column: 在延迟加载查询的时候, 需要传递的字段的值 -->
<association property="user" javaType="user" select="findUserById"
column="user_id">
</association>
</resultMap>
<!-- 查询订单并且关联查询用户信息, 延迟加载用户信息 -->
<select id="findOrdersLazyLoadingUser" resultMap="lazyLoadingUser">
select * from orders
</select>
<select id="findUserById" parameterType="int" resultType="user">
select * from user where id = #{value}
</select>
public interface OrdersMapper {
// 查询订单并且关联查询用户信息, 延迟加载用户信息
List<Orders> findOrdersLazyLoadingUser();
User findUserById(Integer id);
}
测试代码:
public class OrdersMapperTest {
private SqlSessionFactory factory;
@Before
public void getFactory() throws IOException {
this.factory = new SqlSessionFactoryBuilder().build(
Resources.getResourceAsStream("mybatis/sqlMapConfig.xml")
);
}
@Test
// 查询订单并且关联查询用户信息, 延迟加载用户信息
public void findOrdersLazyLoadingUser() {
SqlSession session = factory.openSession();
OrdersMapper ordersMapper = session.getMapper(OrdersMapper.class);
List<Orders> ordersList = ordersMapper.findOrdersLazyLoadingUser();
System.out.println(ordersList);
// 在此处获取用户的信息
for (Orders orders : ordersList) {
User user = orders.getUser();
System.out.println(user);
}
}
日志运行截图:
image.png
Mybatis的缓存
Mybatis提供了一级缓存和二级缓存:
一级缓存
一级缓存是SqlSession级别的缓存
image.png
使用sqlSession去第一次查询数据的时候,会将查询到数据保存到sqlSession中,依赖着一个Map对象,当第二次使用同一个sqlSession对象去查询相同的数据的时候,不会再向数据库发送sql语句。直接从缓存中获取数据。但是如果在这两次查询之间有执行增,删,改的操作。那么session还需要commit()操作,那么这个时候会清除掉SqlSession中的缓存。这是为了保存缓存中的数据始终和数据库中的数据保持一致。一级缓存在mybatis中默认已经开启。
测试一级缓存的存在
@Test
// 测试一级缓存
public void findUserById() {
SqlSession session = factory.openSession();
OrdersMapper ordersMapper = session.getMapper(OrdersMapper.class);
User user1 = ordersMapper.findUserById(28);
System.out.println(user1);
user1.setUsername("study");
// 修改用户信息, 会清除掉SqlSession中的缓存, 第二次查询的时候, 会再次从数据库中查询
ordersMapper.updateUser(user1);
session.commit();
User user2 = ordersMapper.findUserById(28);
System.out.println(user2);
}
二级缓存
当第一个SqlSession查询数据库的时候,会将查询出来的数据放到二级缓存中,然后另外的SqlSession再次查询同样的数据的时候,会先查询二级缓存中的数据。同样在两次查询之间如果有执行commit()操作 那么也会清除二级缓存中的数据
注意: 二级缓存默认是没有开启的,所以必须配置<settings>中的参数,然后在映射文件中添加<cache/>表示开启二级缓存。因为二级缓存的存储介质不一定是内存可能是是存储在硬盘上面,那么需要我们的pojo去实现序列化接口。
实体类实现序列化接口:
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// ...(getter和setter方法省略)
}
在mybatis全局配置文件中开启二级缓存:
<settings>
<!-- 在控制台输出日志信息 -->
<setting name="logImpl" value="LOG4J"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载取消 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
在映射文件中开启二级缓存:
<!--
如果采用的mapper代理开发模式, 那么映射文件的命名空间必须是
Mapper接口的全路径
-->
<mapper namespace="com.wtu.mapper.OrdersMapper">
<!-- 开启二级缓存 -->
<cache/>
<!-- 根据id查询用户信息
如果想要某一条查询语句禁用二级缓存, 那么需要加上
useCache="false" -->
<select id="findUserById" parameterType="int" resultType="user" useCache="true">
select * from user where id = #{value}
</select>
</mapper>
如果想要在执行commit()操作的时候, 不去刷新二级缓存, 那么只需要添加如下代码
image.png
但是一般不会这样,这样会导致二级缓存中的数据和数据库中的数据不同步。
二级缓存测试代码
// 测试二级缓存
@Test
public void testCache2() {
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
SqlSession session3 = factory.openSession();
OrdersMapper mapper1 = session1.getMapper(OrdersMapper.class);
OrdersMapper mapper2 = session2.getMapper(OrdersMapper.class);
OrdersMapper mapper3 = session3.getMapper(OrdersMapper.class);
User user1 = mapper1.findUserById(28);
System.out.println(user1);
session1.close();
// 修改用户信息, 会清除掉二级缓存, 第二次查询的时候, 会再次从数据库中查询
user1.setUsername("learn");
mapper3.updateUser(user1);
session3.commit();
session3.close();
User user2 = mapper2.findUserById(28);
System.out.println(user2);
session2.close();
}
运行成功:
image.png
补充: Spring整合mybatis
准备环境:
spring的jar包
Mybatis的jar包
Mybatis和spring的整合jar包
spring的配置文件
Mybatis的全局配置文件
Mybatis的映射文件
这里先不用Maven, 到后面SSM整合的时候, 我会使用Maven的!
image.png
传统方式DAO开发的整合(需要编写接口, 也需要编写实现类)
1.spring的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!--加载属性文件, 从类路径下直接加载 -->
<context:property-placeholder location="classpath:dbConfig.properties"/>
<!-- 注册c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}"/>
<property name="jdbcUrl" value="${url}"/>
<property name="user" value="${user}"/>
<property name="password" value="${pass}"/>
</bean>
<!-- 注册SqlSessionFactory对象, 由于SqlSessionFactory是个接口, 所以
配置它的实现类
然后mybatis和spring整合包正好提供了一个实现类 SqlSessionFactoryBean -->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/sqlMapConfig.xml"/>
</bean>
<!-- 注册UserDaoImpl对象 -->
<bean id="userDaoImpl" class="com.wtu.ssm.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="factory"/>
</bean>
<!-- mapper代理开发时, 由于没有实现类
MapperFactoryBean:
我们通过该类来产生Mapper接口的代理对象 -->
<!--<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">-->
<!--<!– 我们自己写的Mapper接口的路径 –>-->
<!--<property name="mapperInterface" value="com.wtu.ssm.mapper.UserMapper"/>-->
<!--<property name="sqlSessionFactory" ref="factory"/>-->
<!--</bean>-->
<!-- 通过MapperScannerConfigurer来批量扫描某一个包
生成的每一个代理对象的id为接口名, 并且首字母小写 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wtu.ssm.mapper" />
<!-- 这个方法过时了, 而是需要sqlSessionFactoryBeanName
通过工厂的名字来加载 -->
<!--<property name="sqlSessionFactory" ref="factory"/>-->
<property name="sqlSessionFactoryBeanName" value="factory"/>
</bean>
</beans>
Mybatis的全局配置文件:
<?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"/>-->
<!--</settings>-->
<typeAliases>
<package name="com.wtu.ssm.entity"/>
</typeAliases>
<!-- 加载映射文件 -->
<!--<mappers>-->
<!--<!– 原始dao开发的加载 –>-->
<!--<mapper resource="mybatis/user.xml"/>-->
<!--</mappers>-->
</configuration>
Dao层的实现类:
image.png
在测试类中 需要启动springIOC容器:
image.png
Mapper代理模式的整合(推荐)
1.通过MapperFactoryBean配置单个Mapper接口的代理对象
由于mapper代理开发方式没有实现类 只有接口,然而在spring中注册对象是需要一个类,所以提供了MapperFactoryBean 这么一个类,可以通过这个类来注册代理对象
image.png
该类中必须配置两个属性:
a. mapperInterface:配置自己编写的mapper接口的路径。
b. sqlSessionFactory: 需要配置一个session工厂 ,引入一个工厂对象
然后再springIOC容器中就可以通过 id 来获取mapper接口的代理对象
(推荐)
2.通过MapperScannerConfigurer来批量扫描某一个包,生成该包下所有的mapper接口的代理对象
image.png
代码:
<!-- 通过MapperScannerConfigurer来批量扫描某一个包
生成的每一个代理对象的id为接口名, 并且首字母小写 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.wtu.ssm.mapper" />
<!-- 这个方法过时了, 而是需要sqlSessionFactoryBeanName
通过工厂的名字来加载 -->
<!--<property name="sqlSessionFactory" ref="factory"/>-->
<property name="sqlSessionFactoryBeanName" value="factory"/>
</bean>
注意: 如果采用的是Mapper代理开发方式进行整合的话,就不需要在mybatis的全局配置文件中再去加载映射文件
完整代码地址
https://github.com/menglanyingfei/SSMLearning/tree/master/mybatis_day04
https://github.com/menglanyingfei/SSMLearning/tree/master/spring-mybatis
网友评论