mybatis事务与懒加载和缓存机制
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>
<!--配置数据库信息-->
<environments default="mysql">
<!--数据库编号-->
<environment id="mysql">
<!--配置 jdbc 事务管理器-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源 采用mybatis提供的连接池(POOLED,UNPOOLED,JNDL)-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--xml sql 配置文件映射位置-->
<mappers>
<mapper resource="com/stack/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
mybatis在使用时我们可以通过配置 datasource的 type 属性来决定是否使用数据库连接池。mybatis 内部分别定义和实现了 Java.sql.DataSource 接口的UnPooledDateSource,PooledDateSource 类来表示 UNPOOLED、POOLED的数据源。也就是我们可以在配置中选择我们将要采用的数据源。使用数据连接池可以减少我们频繁的创建释放连接,获取资源。可以提高数据库连接的利用效率,减少我们的资源浪费。
事务
事务是一组sql语句,这组sql语句作为整体来执行,要么全都成功,要么全都失败,具有ACID属性。关于更详细的有关事务的理论我就不在这里过多说明。大家都知道mybatis本身就是对jdbc进行的封装,所以mybatis本身的事务支持也就是 jdbc 的事务支持。
在 jdbc中在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit() [connection对象]方法就可以调整。
mybatis中对事务默认是手动提交的方式,这也符合我们对事务进行控制的特点。
//1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象,手动提交事务 session = factory.openSession(); //5.创建 Dao 的代理对象 userDao = session.getMapper(IUserDao.class); //6.事务提交 session.commit() //7.事务回滚 session. rollback()
mybatis自动提交事务支持
//4.创建 SqlSession 对象,自动提交事务 session = factory.openSession(true);
延迟加载
通过mybatis中一对一,一对多,多对多关系的配置,我们可以实现对象的关联查询。实际应用中很多时候我们并不需要总是在加载用户信息时就加载他相关的账户信息,此时就需要我们所说的延迟加载
延迟加载就是我们需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称为懒加载。
好处:先从单表查询,需要时在去从关联表查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:只有当需要用到数据时,才进行数据库查询,这样在大批量数据查询时,因为查询工作也需要时间,所以可能造成用户等待时间变长,造成用户体验下降。
/**
需求:查询账户 Account 信息并且关联查询用户 User 信息。如果先查询账户 Account 信息即可满足要求,当我们需要查询用户 User 信息时再查询用户 User 信息。把对用户 User 信息的按需查询就是延迟加载。
mybatis在实现多表操作时,我们使用 resultMap 来实现 一对一,一对多,多对多关系操作,主要用过 association collection 实现 一对一 一对多 映射。因此 association collection 具备延迟加载的功能
*/
使用assocation 实现延迟加载
查询账户信息同时延迟加载查询其用户信息
mapper接口
public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
List<Account> findAll();
}
public interface IUserDao {
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
}
map映射配置文件
<?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.stack.dao.IAccountDao">
<!-- 建立对应关系 -->
<resultMap type="com.stack.bean.Account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 -->
<association property="user" javaType="user"
select="com.stack.dao.IUserDao.findById"
column="uid">
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
</mapper>
select: 填写我们要调用的 select 映射的 id
column : 填写我们要传递给 select 映射的参数
<?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.stack.dao.IUserDao">
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" >
select * from user where id = #{uid}
</select>
</mapper>
在mybatis环境中配置(SqlMapConfig)开启 mybatis 延迟加载的设置
<settings>
<!--开启延迟加载可用-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭延迟加载不可用-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
编写测试类
public class AccountTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IAccountDao accountDao;
@Before//在测试方法执行之前执行
public void init()throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建 SqlSession 工厂对象
factory = builder.build(in);
//4.创建 SqlSession 对象
session = factory.openSession();
//5.创建 Dao 的代理对象
accountDao = session.getMapper(IAccountDao.class);
}
@Test
public void testFindAll() {
//6.执行操作
List<Account> accounts = accountDao.findAll();
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
//7.释放资源
session.close();
in.close();
}
}
测试结果
本次只是将Account对象查询出来放入List集合中,并没有涉及到 User 对象,所以就没有发出Sql语句查询账户所关联的User信息。延迟加载生效。
使用collection 实现延迟加载
查询用户信息同时延迟加载查询其相关账户信息
用户实体类
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Account> accounts;
//get set 无参构造方法省略
}
编写mapper接口相关方法
/**
*查询所有用户信息
*/
List<User> findAll();
/**
* 根据用户 id 查询账户信息
* @param uid
* @return
*/
List<Account> findByUid(Integer uid);
map映射配置文件
<resultMap type="com.stack.bean.User" id="userMap">
<id column="id" property="id"></id>
<result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询
-->
<collection property="accounts" ofType="account"
select="com.stack.dao.IAccountDao.findByUid"
column="id">
</collection>
</resultMap>
<!-- 配置查询所有操作 -->
<select id="findAll" resultMap="userMap">
select * from user
</select>
collection标签:
主要用于加载关联的集合对象
select 属性:
用于指定查询 account 列表的 sql 语句,所以填写的是该 sql 映射的 id
column 属性:
用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一
个字段名了
<!-- 根据用户 id 查询账户信息 -->
<select id="findByUid" resultType="com.stack.bean.Account" parameterType="int">
select * from account where uid = #{uid}
</select>
编写测试类
public class UserTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Before//在测试方法执行之前执行
public void init()throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建 SqlSession 工厂对象
factory = builder.build(in);
//4.创建 SqlSession 对象
session = factory.openSession();
//5.创建 Dao 的代理对象
userDao = session.getMapper(IUserDao.class);
}
@Test
public void testFindAll() {
//6.执行操作
List<User> users = userDao.findAll();
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
session.commit();
//7.释放资源
session.close();
in.close();
}
}
测试结果
我们发现并没有加载 Account 账户信息。延迟加载成功。
mybatis缓存机制
mybatis作为一个持久层的ORM框架,同样向我们提供了缓存策略。通过缓存机制可以减少我们数据库的查询次数,从而提高性能。mybatis的缓存机制分为两级,一级缓存是SqlSession级别的,也就是针对一个SqlSession会话,当SqlSession.close()掉或重新获取SqlSession对象,一级缓存也就消失没有了。二级缓存是NameSpace级别的,针对于一个NameSpace配置。作用范围大于SqlSession。
一级缓存测试
一级缓存是SqlSession级别的缓存,只要SqlSession没有flush 或 close,它就存在
public interface IUserDao {
/**
* 根据 id 查询
* @param userId
* @return
*/
User findById(Integer userId);
}
<?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.stack.dao.IUserDao">
<!-- 根据 id 查询 -->
<select id="findById" resultType="con.stack.bean.User" parameterType="int">
select * from user where id = #{uid}
</select>
</mapper>
public class UserTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Test
public void testFindById() {
//6.执行操作
User user = userDao.findById(41);
System.out.println("第一次查询的用户:"+user);
User user2 = userDao.findById(41);
System.out.println("第二次查询用户:"+user2);
System.out.println(user == user2);
}
@Before//在测试方法执行之前执行
public void init()throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建 SqlSession 工厂对象
factory = builder.build(in);
//4.创建 SqlSession 对象
session = factory.openSession();
//5.创建 Dao 的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
//7.释放资源
session.close();
in.close();
}
}
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提
供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句
从数据库中查询数据,而是从一级缓存中查询。
二级缓存测试
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的Sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。注意点:当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
二级缓存的使用
SqlMapConfig中开启二级缓存支持
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?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.stack.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
<!-- 根据 id 查询 -->
<select id="findById" resultType="com.stack.bean.User" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存
public class SecondLevelCacheTest {
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{
in.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2);
} }
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二
次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。
补充
如果使用注解开发的话,要想使用二级缓存的话,就在Mapper映射接口上使用@CacheNamespace注解就可以。