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 { }