MyBatis中的延迟加载。
延迟加载和立即加载
延迟加载:真正使用数据时候才发起查询,不用的时候不查询,按需加载(懒加载)。
立即加载:不管用不用,只要一调用方法,马上发起查询。
在对应的四种表关系中,一对多、多对多使用 延迟加载。多对一、一对一采用立即加载。
即看屁股,屁股大的延迟加载。
实现
一对一实现延迟加载
需求:当查询账户信息时使用延迟加载。也就是说,如果不需要使用用户信息的话,那么只查询账户信息;只有当需要使用用户信息时,才去关联查询。
-
在MyBatis的配置文件中开启延迟加载
1 2 3 4 5
| <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
|
-
修改上一篇笔记编写好的账户映射文件 AccountMapper.xml
1 2 3 4 5 6 7 8 9 10 11
| <mapper namespace="top.tyzhang.dao.IAccountDao"> <resultMap id="accountUserMap" type="top.tyzhang.domain.Account"> <id property="id" column="id"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="top.tyzhang.domain.User" select="top.tyzhang.dao.IUserDao.findById"></association> </resultMap> <select id="findAll" resultMap="accountUserMap"> SELECT * from account </select> </mapper>
|
- 标签中的
select
属性表示我们要调用的映射语句的 ID,它会从 column
属性指定的列中检索数据,作为参数传递给目标 select 语句。
column
属性指定传递给我们要调用的映射语句的参数。
一对多实现延迟加载
-
IUserDao映射文件
1 2 3 4 5 6 7 8 9 10 11 12
| <mapper namespace="top.tyzhang.dao.IUserDao"> <resultMap id="userAccountMap" type="top.tyzhang.domain.User"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <collection property="accounts" ofType="top.tyzhang.domain.Account" select="top.tyzhang.dao.IAccountDao.findAccountByUid" column="uid"></collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> select * from userm </select> </mapper>
|
-
在账户实体类添加对应方法
1 2 3 4
| public interface IAccountDao { List<Account> findAll(); List<Account> findAccountByUid(Integer uid); }
|
1 2 3 4 5 6 7 8 9 10 11
| <mapper namespace="top.tyzhang.dao.IAccountDao"> <resultMap id="accountUserMap" type="top.tyzhang.domain.Account"> <id property="id" column="id"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="top.tyzhang.domain.User" select="top.tyzhang.dao.IUserDao.findById"></association> </resultMap> <select id="findAccountByUid" resultType="top.tyzhang.domain.Account"> SELECT * from account where uid = #{uid} </select> </mapper>
|
-
测试类
1 2 3 4 5 6 7
| public void findAll() { List<Account> accounts = accountDao.findAll(); for (Account account : accounts) { System.out.println(account); System.out.println(account.getUser()); } }
|
当只执行findAll时候,日志中只调用了SELECT * from account
,如果打印语句执行,在打印循环体中还会逐个执行select * from userm where id = ?
。
缓存
WHAT
:存在于内存的临时数据。
WHY
:为了减少和数据库交互的次数,提高执行效率。
HOW
:适用:经常查询并且不经常改变的数据。数据的正确与否对最终结果影响不大的。不适用:经常改变的数据。数据的正确与否对最终结果影响很大的。例如:商品的库存、银行的汇率、股市的牌价等。
一级缓存
指的是MyBatis中SqlSession对象的缓存,当我们执行查询后,查询的结果会同时存入SqlSession为我们提供的一块区域中。该区域的结构是一个Map,当我们再次查询同样的数据,MyBatis会先去SqlSession中查询是否有,有的话直接拿过来用。当SqlSession对象消失时,MyBatis的一级缓存也就消失了。
1 2 3 4
| User user1 = userDao.findById(41); User user2 = userDao.findById(41); System.out.println(user1 == user2);
|
1 2 3 4 5 6 7 8 9
| User user1 = userDao.findById(41);
sqlSession.close(); sqlSession = factory.openSession();
userDao = sqlSession.getMapper(IUserDao.class); User user2 = userDao.findById(41); System.out.println(user1 == user2);
|
当调用SqlSession的修改、添加、删除、commit()、close()等方法时,就会清空一级缓存。例如以上例子如果在user2前update user1,输出false。
二级缓存
指的是MyBatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享缓存。
使用步骤
-
让MyBatis框架支持二级缓存,在SqlMapConfig.xml中配置。
1 2 3 4
| <settings> <setting name="cacheEnabled" value="true"/> </settings>
|
-
让当前的映射文件支持二级缓存,在IUserDao.xml中配置。
-
让当前的操作支持二级缓存,在select标签中配置。
1 2 3
| <select id="findAll" resultMap="userAccountsMap" useCache="true"> SELECT * FROM user </select>
|
- 当我们使用二级缓存的时候,所缓存的类一定要实现
java.io.Serializable
接口,这样才可以使用序列化的方式来保存对象。
- 由于是序列化保存对象,所以二级缓存中存放的是数据,而不是整个对象。
1 2 3 4 5 6 7 8 9 10 11 12
| SqlSession sqlSession1 = factory.openSession(); IUserDao dao1 = sqlSession1.getMapper(IUserDao.class); User user1 = dao1.findById(41); sqlSession.close();
SqlSession sqlSession2 = factory.openSession(); IUserDao dao2 = sqlSession2.getMapper(IUserDao.class); User user2 = dao2.findById(41); sqlSession.close();
System.out.println(user1 == user2);
|
基于注解开发
常用注解
注解 |
作用 |
@Intsert |
实现新增 |
@Update |
实现更新 |
@Delete |
实现删除 |
@Select |
实现查询 |
@Results |
实现结果集封装 |
@ResultMap |
实现引用 @Results 定义的封装 |
@One |
实现一对一结果集封装 |
@Many |
实现一对多结果集封装 |
@SelectProvider |
实现动态 SQL 映射 |
@CacheNamespace |
实现注解二级缓存的使用 |
环境搭建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?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 resource="jdbcConfig.properties"></properties> <typeAliases> <package name="top.tyzhang.domain"></package> </typeAliases> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <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> </environments> <mappers> <package name="top.tyzhang.dao"></package> </mappers> </configuration>
|
CRUD
接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public interface IUserDao { @Select("select * from userm") List<User> findAll();
@Insert("insert into userm(username, address, sex, birthday) values (#{username}, #{address}, #{sex}, #{birthday})") void saveUser(User user);
@Update("update userm set username=#{username}, sex=#{sex}, birthday=#{birthday}, address=#{address} where id = #{id}") void updateUser(User user);
@Delete("delete from userm where id =#{id}") void deleteUser(Integer id);
@Select("select * from userm where id =#{id}") User findById(Integer id);
@Select("select * from userm where username like #{username}") List<User> findUserByName(String username);
@Select("select * from userm where username like '%${value}%'") List<User> findUserByName1(String username);
@Select("select count(*) from userm") int findTotal(); }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public class annocrud { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao;
@Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); userDao = session.getMapper(IUserDao.class); } @After public void destroy() throws IOException { session.commit(); session.close(); in.close(); } @Test public void testSave(){ User user = new User(); user.setUsername("张天宇"); user.setAddress("山东省临沂市"); userDao.saveUser(user); }
@Test public void testUpdate(){ User user = new User(); user.setId(49); user.setUsername("张天宇"); user.setAddress("浙江省杭州市"); user.setSex("男"); userDao.updateUser(user); }
@Test public void testDelete(){ userDao.deleteUser(49); }
@Test public void testFindOne(){ System.out.println(userDao.findById(48)); }
@Test public void testFindByName(){ List<User> users = userDao.findUserByName("%王%"); for (User user:users) System.out.println(user); }
@Test public void testFindByName1(){ List<User> users = userDao.findUserByName1("王"); for (User user:users) System.out.println(user); }
@Test public void testFindTotal(){ System.out.println(userDao.findTotal()); } }
|
注意
当实体类的属性与数据库表列名不一致,应该使用@Results、@Result、@ResultMap
等注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface IUserDao { @Select("SELECT * FROM user") @Results(id = "UserMap",value = { @Result(id = true, property = "userId",column = "id"), @Result(property = "userName",column = "username"), @Result(property = "userBirthday",column = "birthday"), @Result(property = "userSex",column = "sex"), @Result(property = "userAddress",column = "address"), }) List<User> listAllUsers(); @Insert("INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})") @ResultMap("UserMap") int saveUser(User user); }
|
@Results
注解用于定义映射结果集,相当于标签 。
其中,id 属性为唯一标识。value 属性用于接收 @Result[]
注解类型的数组。
@Result
注解用于定义映射关系,相当于标签 和
其中,id 属性指定主键。property 属性指定实体类的属性名,column 属性指定数据库表中对应的列。
@ResultMap
注解用于引用 @Results
定义的映射结果集,避免了重复定义映射结果集。
一对一立即加载
1 2 3 4 5 6 7 8
| public class Account implements Serializable { private Integer id; private Integer uid; private Double money; private User user; }
|
1 2 3 4 5 6 7 8 9 10
| public interface IAccountDao { @Select("select * from account") @Results(id="accountMap", value={ @Result(id=true, column = "id", property = "id"), @Result(column = "uid", property = "uid"), @Result(column = "money", property = "money"), @Result(property = "user", column = "uid", one=@One(select="top.tyzhang.dao.IUserDao.findById", fetchType= FetchType.EAGER)) }) List<Account> findAll(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class accounttest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IAccountDao accountDao;
@Before public void init() throws IOException { in = Resources.getResourceAsStream("SqlMapConfig.xml"); factory = new SqlSessionFactoryBuilder().build(in); session = factory.openSession(); accountDao = session.getMapper(IAccountDao.class); } @After public void destroy() throws IOException { session.commit(); session.close(); in.close(); } @Test public void testFindAll(){ List<Account> accounts = accountDao.findAll(); for (Account account:accounts) { System.out.println(account); System.out.println(account.getUser()); } } }
|
@One
注解相当于标签 ,是多表查询的关键,在注解中用来指定子查询返回单一对象。其中,select 属性指定用于查询的接口方法,fetchType 属性用于指定立即加载或延迟加载,分别对应 FetchType.EAGER 和 FetchType.LAZY。
- 在包含 @one 注解的
@Result
中,column 属性用于指定将要作为参数进行查询的数据库表列。
一对多延迟加载
1 2 3 4 5 6 7 8 9 10
| public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; private List<Account> accounts; }
|
1 2 3 4 5 6 7 8
| public interface IUserDao { @Select("select * from userm") @Results(value = {@Result(id=true, column = "id", property = "id"), @Result(property = "accounts", column = "id", many = @Many(select = "top.tyzhang.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY))} ) List<User> findAll(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface IAccountDao {
@Select("select * from account") @Results(id="accountMap", value={ @Result(id=true, column = "id", property = "id"), @Result(column = "uid", property = "uid"), @Result(column = "money", property = "money"), @Result(property = "user", column = "uid", one=@One(select="top.tyzhang.dao.IUserDao.findById", fetchType= FetchType.EAGER)) }) List<Account> findAll(); @Select("select * from account where uid = #{uid}") List<Account> findAccountByUid(Integer uid); }
|
开启二级缓存
1 2 3 4
| <settings> <setting name="cacheEnabled" value="true"/> </settings>
|
在持久层配置
1 2 3 4
| @CacheNamespace(blocking = true) public interface IUserDao { }
|