一、MyBatis加载策略
1.什么是延迟加载?
问题:Mybatis中一对一,一对多,多对多关系的配置及实现,可以实现 对象的关联查询。实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。 举个栗子
在一对多中,当我们有一个用户,它有个100个订单
在查询用户的时候,要不要把关联的订单查出来?
在查询订单的时候,要不要把关联的用户查出来?
答:
在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。
在查询订单时,订单所属的用户信息应该是随着订单一起查询出来。
**延迟加载:**就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载
优点:
先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
缺点:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
在多表中
一对多,多对多:通常情况下采用延迟加载
一对一(多对一):通常情况下采用立即加载
注意:
延迟加载是基于嵌套查询来实现的
区别:
当前对象它的关联对象的加载时机:
如果它的关联对象在查询当前对象立刻查询出来就是立即加载;
关联对象什么时候用什么时候去查就就是延迟加载;
2.实现局部延迟加载
2.1 局部延迟加载
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。
<resultMap id="userMap2" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="ordersList" ofType="orders" fetchType="lazy"
select="cn.xuguowen.mapper.OrdersMapper.findById" column="id"/>
</resultMap>
<select id="findAllWithOrders2" resultMap="userMap2">
select * from user
</select>
2.2 设置触发延迟加载的方法
我发现在配置了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的equals、clone、hashCode、toString方法时也会触发关联对象的查询。 我们可以在核心配置文件中使用lazyLoadTriggerMethods配置项覆盖掉上面四个方法。
<settings>
<setting name="lazyLoadTriggerMethods" value="toString()"/>
</settings>
3.实现全局延迟加载
在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。
<settings>
<setting name="lazyLoadTriggerMethods" value="toString()"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
注意 局部的加载策略优先级高于全局的加载策略
<resultMap id="ordersMap2" type="orders">
<id property="id" column="id"/>
<result property="ordertime" column="ordertime"/>
<result property="total" column="total"/>
<result property="uid" column="uid"/>
<association property="user" javaType="user" fetchType="eager"
select="cn.xuguowen.mapper.UserMapper.findById" column="uid"/>
</resultMap>
<select id="findAllWithUser2" resultMap="ordersMap2">
select * from orders
</select>
二、MyBatis缓存
1.为什么要使用缓存?
当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询。减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题。 一句化概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。 像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。
2.一级缓存
一级缓存是SqlSeesion级别的缓存,是默认开启的 所以在参数和SQL语句完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
@Test
public void testOneCache() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.findById(1);
System.out.println(user1);
User user2 = mapper.findById(1);
System.out.println(user2);
}
注意 一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存,避免脏读。 每次查询时,都会清除缓存
<select flushCache="true"></select>
3.二级缓存
二级缓存是namspace级别(跨sqlSession)的缓存,是默认不开启的 二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置 就可以开启二级缓存了。
核心配置文件
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
配置UserMapper.xml文件
<cache></cache>
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{id}
</select>
修改User实体
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private Character sex;
private String address;
private List<Orders> ordersList;
private List<Role> roleList;
测试结果
@Test
public void testTwoCache() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = factory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.findById(1);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = factory.openSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.findById(1);
System.out.println(user2);
sqlSession2.close();
}
4.二级缓存的分析
5.二级缓存所产生的严重问题(脏读)
**mybatis的二级缓存因为是namespace级别,所以在进行多表查询时会产生脏读问题 **
6.小结
1. mybatis的缓存,都不需要我们手动存储和获取数据。mybatis自动维护的。
2. mybatis开启了二级缓存后,那么查询顺序:二级缓存--》一级缓存--》数据库
2. 注意:mybatis的二级缓存会存在脏读问题,需要使用第三方的缓存技术解决问题。
三、MyBatis注解开发
1.MyBatis常用注解
* @Insert:实现新增,代替了<insert></insert>
* @Delete:实现删除,代替了<delete></delete>
* @Update:实现更新,代替了<update></update>
* @Select:实现查询,代替了<select></select>
* @Result:实现结果集封装,代替了<result></result>
* @Results:可以与@Result 一起使用,封装多个结果集,代替了<resultMap></resultMap>
* @One:实现一对一结果集封装,代替了<association></association>
* @Many:实现一对多结果集封装,代替了<collection></collection>
2.MyBatis注解开发实现增删改查
①:UserMapper接口
package cn.xuguowen.mapper;
import cn.xuguowen.pojo.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface UserMapper {
@Select("select * from user")
List<User> findAll();
@Insert("insert into user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex},#{address} )")
void save(User user);
@Update("update user set username = #{username},birthday = #{birthday} where id = #{id}")
void update(User user);
@Delete("delete from user where id = #{id}")
void delete(Integer id);
}
②:核心配置文件
<mappers>
<package name="com.lagou.mapper"></package>
</mappers>
③:测试
package cn.xuguowen.test;
import cn.xuguowen.mapper.UserMapper;
import cn.xuguowen.pojo.User;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
public class TestAnno {
private SqlSessionFactory factory;
private SqlSession sqlSession;
@Before
public void before() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(is);
sqlSession = factory.openSession();
}
@After
public void after() {
sqlSession.commit();
sqlSession.close();
}
@Test
public void testFindAll() throws IOException {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testSave() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("徐国文");
user.setBirthday(new Date());
user.setSex('男');
user.setAddress("山西大同");
mapper.save(user);
}
@Test
public void testUpdate() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("黄老板");
user.setBirthday(new Date());
user.setId(6);
mapper.update(user);
}
@Test
public void testDelete() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.delete(8);
}
}
3.MyBatis注解开发实现复杂映射开发
之前我们在映射文件中通过配置 resultMap、association、collection 来实现复杂关系映射。 使用注解开发后,我们可以使用@Results、@Result、@One、@Many注解组合使用完成复杂关系的配置
注解 | 说明 |
---|
@Results | 代替的标签是resultMap,该注解中可以使用单个@Result注解,也可以使用@Result集合。使用格式:@Results({@Result(),@Result(),@Result(),@Result()}) | @Result | 代替了标签id和result。@Result中属性的介绍:column:数据库的列名,property需要装配的属性名,one需要使用@One注解( @Result(one=@One()) ),many需要使用@Many注解( @Result(many=@Many()) ) | @One (一对一) | 代替了assocation标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。@One注解属性的介绍:select指定用来查询的sqlmapper,其实就是namespace.id来确定是哪个mapper中的哪个sql语句。使用格式:@Result(column=" “,property=” “,one=@One(select=” ") ) | @Many(一对多) | 代替了collection标签,是多表查询的关键,在注解中用来指定子查询返回对象集合 。使用格式:@Result(property=" “,column=” “,many=@Many(select=” ") ) |
4.MyBatis注解开发实现一对一查询
4.1 需求:查询订单信息,与此同时查询出该订单所属的用户信息
一对一查询语句
SELECT * FROM orders;
SELECT * FROM `user` WHERE id =
4.2 代码实现
a)OrderMapper接口和UserMapper接口
@Select("select * from orders")
@Results({
@Result(property = "id",column = "id"),
@Result(property = "ordertime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "uid",column = "uid"),
@Result(property = "user",javaType = User.class,column = "uid",
one = @One(select = "cn.xuguowen.mapper.UserMapper.findById", fetchType = FetchType.EAGER))
})
List<Orders> findAllWithUser();
@Select("select * from user where id = #{uid}")
User findById(Integer uid);
b)测试代码
@Test
public void testOneToOne() {
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List<Orders> allWithUser = mapper.findAllWithUser();
for (Orders orders : allWithUser) {
System.out.println(orders);
}
}
5.MyBatis注解开发实现一对多查询
5.1 需求:查询所有用户,以及该用户所具有的订单信息
一对多查询语句
SELECT * FROM user;
SELECT * FROM `orders` WHERE uid =
5.2 代码实现
a)UserMapper接口和OrdersMapper接口
@Select("select * from user")
@Results({
@Result(property = "id",column = "id",id = true),
@Result(property = "username",column = "username"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address"),
@Result(property = "ordersList",javaType = List.class,column = "id",
many = @Many(select = "cn.xuguowen.mapper.OrderMapper.findById") )
})
List<User> findAllWithOrders();
@Select("select * from orders where uid = #{uid}")
List<Orders> findById(Integer uid);
b)测试代码
@Test
public void testOneToMany() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findAllWithOrders();
for (User user : users) {
System.out.println(user);
System.out.println(user.getOrdersList());
}
}
6.MyBatis注解开发实现多对多查询
6.1 需求:查询所有用户,同时查询出该用户的所有角色
多对多查询语句
SELECT * FROM user;
SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON r.id = ur.roleid WHERE ur.userid =
6.2 代码实现
a)UserMapper接口和RoleMapper接口
@Select("select * from user")
@Results({
@Result(property = "id",column = "id",id = true),
@Result(property = "username",column = "username"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address"),
@Result(property = "roleList",javaType = List.class,column = "id",
many = @Many(select = "cn.xuguowen.mapper.RoleMapper.findByUserId"))
})
List<User> findAllWithRole();
@Select("SELECT * FROM sys_role r INNER JOIN sys_user_role ur ON r.id = ur.roleid WHERE ur.userid = #{uid}")
List<Role> findByUserId(Integer uid);
b)测试代码
@Test
public void testManyToMany() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.findAllWithRole();
for (User user : users) {
System.out.println(user);
System.out.println(user.getRoleList());
}
}
7.MyBatis注解开发实现二级缓存
7.1配置核心配置文件开启二级缓存的支持
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
7.2 在Mapper接口中使用注解配置二级缓存
@CacheNamespace
public interface UserMapper {...}
8.MyBatis注解开发实现延迟加载策略
不管是一对一还是一对多,在注解配置中都有fetchType的属性
* fetchType = FetchType.LAZY 表示懒加载
* fetchType = FetchType.EAGER 表示立即加载
* fetchType = FetchType.DEFAULT 表示使用全局配置,如果全局延迟加载策略的值为true,表示使用延迟加载策略;如果全局延迟加载策略的值为false,表示使用立即加载策略。
* <!--全局延迟加载策略--> <setting name="lazyLoadingEnabled" value="true"/>
9.小结
注解开发和xml映射文件配置优劣分析
1.从开发效率来说:注解编写更简单,效率更高。
2.从可维护性来说:注解如果要修改,必须修改源码,会导致维护成本增加。xml映射配置文件维护性更强。
3.在实际开发中:两种开发方式结合使用。单表CURD可以使用注解开发;复杂查询可以使用xml映射配置文件的方式开发。
|