延迟加载
实现多对一的延迟加载(association)
例如下面的:有很多个账户信息(招商工商农商)是属于一个用户人的
【需求】
查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,
当我们需要查询用户(User)信息时,再去查询用户信息。
第一步:创建工程
第二步:导入配置文件
第三步:Account.java
【需求】:查询账户信息同时查询用户信息。
那么大概要用到是两对Dao和Dao.xml以及两个实体类
Account实体类中加入一个User类的对象
/***/public class Account implements Serializable {private Integer id;private Integer uid;private Double money;private User user;public User getUser() {return user;}public void setUser(User user) {this.user = user;}}
第四步:AccountDao.java
AccountDao类中添加查询账户信息的方法:
/*** 查询账号和客户的信息*/List<Account> findByAccountUser();
第五步:AccountDao.xml
<!--定义resultMap对象,用来封装账号信息--><resultMap id="accountMap" type="account"><id property="id" column="id"></id><result property="uid" column="uid"></result><result property="money" column="money"></result><!--association用来关联对象,property代表加载对象,javaType代表加载对象的数据类型,可以写成com.it.domain.Userselect 属性指定的内容:查询用户的唯一标识,指延迟加载要执行的statement的id要使用UserDao.xml中的findById完成根据用户id查询用户信息column 属性指定的内容:用户根据id查询时,所需要的参数的值--><association property="user" column="uid" javaType="user" select="com.it.dao.UserDao.findById"></association></resultMap><!-- 查询所有账号 --><select id="findByAccountUser" resultMap="accountMap">select * from account;</select>
第六步:UserDao.java
/*** 根据id查询用户信息* @param id* @return*/User findById(Integer id);
第七步:UserDao.xml
<!-- 根据id查询用户 --><select id="findById" parameterType="INT" resultType="com.it.domain.User">select * from user where id = #{uid}</select>
第八步:测试MybatisTest.java
/*** 查询所有账户和客户信息(方案二,直接使用Account对象)*/@Testpublic void testFindAllAccountUser(){//5.执行查询所有方法List<Account> list = accountDao.findByAccountUser();}
测试:
查看sql语句,并没有实现延迟检索,而是立即检索(默认)。
即【查询账号信息,同时把用户的信息也查询】 
第九步:开启Mybatis的延迟检索策略
进入Mybaits的官方文档,找到settings的说明信息
http://www.mybatis.org/mybatis-3/ 
我们发现lazyLoadingEnabled属性的默认是false,表示不会延迟加载。 
配置SqlMapConfig.xml
这里注意:标签的加载顺序:settings需放到typeAliases的前面
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="jdbcConfig.properties"></properties><!--需要配置延迟加载策略--><settings><!--打开延迟加载的开关--><setting name="lazyLoadingEnabled" value="true"/><!--将积极加载改为消息加载,即按需加载--><setting name="aggressiveLazyLoading" value="false"/></settings>
第十步:测试MybatisTest.java
/*** 查询所有账户和客户信息(方案二,直接使用Account对象)*/@Testpublic void testFindAllAccountUser(){//5.执行查询所有方法List<Account> list = accountDao.findByAccountUser();}
查看控制台,此时因为没有查询用户信息,所以只查询了账号的信息。 
我们发现,因为本次只是将Account对象查询出来放入List集合中,并没有涉及到User对象,所以就没有发出SQL语句查询账户所关联的User对象的查询。
我们也可以在AccountDao.xml中使用fethcType属性用来控制延迟检索和立即检索:
fetchType: eager:立即检索 lazy:延迟检索
<association property="user" javaType="user" select="com.it.dao.UserDao.findById" column="uid" fetchType="eager"></association>
实现一对多的延迟加载(collection)
第一步:User.java
【需求】:查询用户信息同时查询账号信息。
一个人用户下有多个账号((招商工商农商),通常一对多,这个多的账号信息用List封装起来对应Colletion
User实体类中加入一个List<Account>类的对象public class User implements Serializable {private Integer id;private String username;private String address;private String sex;private Date birthday;private List<Account> accounts;public List<Account> getAccounts() {return accounts;}public void setAccounts(List<Account> accounts) {this.accounts = accounts;}}
第二步:UserDao.java
UserDao类中添加查询用户信息和账号信息的方法:
/*** 根据用户账号的信息* @param* @return*/List<User> findUserAccountList();
第三步:UserDao.xml
<!--定义用户和账号的查询--><resultMap id="userMap" type="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><result property="birthday" column="birthday"></result><collection property="accounts" ofType="account" select="com.it.dao.AccountDao.findAccountByUid" column="id"></collection></resultMap><!-- 根据用户和账号的信息 --><select id="findUserAccountList" resultMap="userMap">SELECT * FROM user</select>
select属性用于指定查询account列表的sql语句,所以填写的是该sql映射的id
column属性用于指定select属性的sql语句的参数来源,上面的参数来自于user的id列,所以就写成id这一个字段名了
fetchType:立即和延迟检索的开关
lazy:延迟检索
eager:立即检索
第四步:AccountDao.java
/*** 根据用户id查询账号信息* @return*/List<Account> findAccountByUid(Integer uid);
第五步:AccountDao.xml
<!-- 更加用户id查询账号列表 --><select id="findAccountByUid" resultType="account" parameterType="int">select * from account where uid = #{uid};</select>
第六步:测试MybatisTest.java
/*** 查询所有用户和账号信息*/@Testpublic void testFindUserAccountList(){//5.执行查询所有方法List<User> list = userDao.findUserAccountList();for(User user : list){System.out.println(user);System.out.println(user.getAccounts().size());}}
测试:
即【查询用户信息,采用延迟检索,需要账号的信息,才查询账号】 
小结:延迟检索会对系统查询数据库做性能的优化
多对一,一对一的查询,默认是立即检索(不管用没用到一的一端的属性值,因为只查询一次,所以立即检索不会浪费太多性能)
一对多,多对多的查询,默认是延迟检索(不管用没用到多的一端的属性值,因为会查询多次,所以立即检索会浪费太多性能)
总结:通过本示例,我们可以发现Mybatis的延迟加载还要有很明显效果,对于提升软件性能这是一个不错的手段。
用的时候:一般项目经理采用延迟检索,用的时候再查询数据库,有助于程序健壮,减少操作数据库的频率。
如果我们开发,大家记住2点:
1:可以使用联合查询语句,完成多表的查询(用的比较多,联合查询的sql语句不会出现立即检索和延迟检索)
2:可以使用select的单独查询(通过主外键),完成多表的查询(考虑立即检索和延迟检索)
Mybatis缓存
像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。
Mybatis中缓存分为一级缓存,二级缓存
一级缓存:是SqlSession级别的缓存(线程级别的缓存)
二级缓存:是SqlSessionFactory级别的缓存(进程级别的缓存)
一个SqlSessionFactory存在多个SqlSession。
一级缓存
指的是Mybatis中SqlSession对象的缓存
当SqlSession对象消失时,mybatis的一级缓存也就消失了。
两个对象不是同一个对象,SqlSession关闭,缓存消失,会再次查询数据库。
一级缓存是SqlSession 范围的缓存,当调用SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。 
第一次发起查询用户id 为1 的用户信息,先去找缓存中是否有id 为1 的用户信息,如果没有,从数据库查询用户信息。 得到用户信息,将用户信息存储到一级缓存中。 如果sqlSession 去执行commit 操作(执行插入、更新、删除),清空SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id 为1 的用户信息,先去找缓存中是否有id 为1 的用户信息,缓存中有,直接从缓存中获取用户信息。
二级缓存
指的是Mybatis中SqlSessionFactory对象的级别缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在UserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
二级缓存结构图 
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。
但是为什么2个对象不一致呢?原理二级缓存中存放的是对象的散装数据,每次查询的时候需要重新封装实体对象。 
SqlSessionFactory存放缓存的地方: 
考虑二级缓存的应用场景:
适应放置到二级缓存的数据:经常不会发生变化的数据,例如地区编码
不适合放置到二级缓存的数据:经常变化的数据
--------------------------------财务数据
Mybatis注解开发
查询所有
把所有的接口配置文件都删除了
实体类
src/main/java,创建包com.it.domain,创建User.java
public class User implements Serializable{private Integer id;private String username;private String address;private String sex;private Date birthday;}
接口
Src/main/java,创建包com.it.dao,创建UserDao.java
/*** 在mybatis中针对,CRUD一共有四个注解* @Select @Insert @Update @Delete*/public interface UserDao {/*** 查询所有用户* @return*/@Select(value="select * from user")List<User> findAll();}
测试MybatisTest.java
/***/public class MybatisAnnoTest {/*** 测试基于注解的mybatis使用* @param args*/public static void main(String[] args) throws Exception{//1.获取字节输入流InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");//2.根据字节输入流构建SqlSessionFactorySqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);//3.根据SqlSessionFactory生产一个SqlSessionSqlSession session = factory.openSession();//4.使用SqlSession获取Dao的代理对象UserDao userDao = session.getMapper(UserDao.class);//5.执行Dao的方法List<User> users = userDao.findAll();for(User user : users){System.out.println(user);}//6.释放资源session.close();in.close();}}
使用Mybatis注解实现基本CRUD
UserDao.java
/*** 在mybatis中针对,CRUD一共有四个注解* @Select @Insert @Update @Delete*/public interface UserDao {/*** 查询所有用户* @return*/@Select("select * from user")List<User> findAll();/*** 保存用户* @param user*/@Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")@SelectKey(statement = "SELECT LAST_INSERT_ID()",keyProperty = "id",keyColumn = "id",before = false,resultType = Integer.class)void saveUser(User user);/*** 更新用户* @param user*/@Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")void updateUser(User user);/*** 删除用户* @param userId*/@Delete("delete from user where id=#{id} ")void deleteUser(Integer userId);/*** 根据id查询用户* @param userId* @return*/@Select("select * from user where id=#{uid} ")User findById(@Param(value = "uid")Integer userId);/*** 根据用户名称模糊查询* @param username* @return*/// @Select("select * from user where username like #{username} ")@Select("select * from user where username like '%${value}%' ")List<User> findUserByName(String username);/*** 查询总用户数量* @return*/@Select("select count(*) from user ")int findTotalUser();// 传递2个条件或者多个条件@Select(value = "select * from user where username like #{name} and sex = #{sex}")List<User> findByNameAndSex(@Param(value = "name") String username, @Param(value = "sex") String sex);@Select(value = "select * from user where username like #{name} and sex = #{sex}")List<User> findByNameAndSex2(Map map);}
当遇到传递非单个参数时需要注意:
当使用@Param标签作为参数时,测试类只要按照参数的顺序传入即可.
当使用map作为参数导入时,需注意map的key的名字需与注释中设置的{}中的一样 
使用注解实现复杂关系映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置
修改 UserDao.java
添加注解:@Results和@Result,在其他的方法中,可以使用@ResultMap指定id的名称。
/*** 在mybatis中针对,CRUD一共有四个注解* @Select @Insert @Update @Delete*/public interface UserDao {/*** 查询所有用户* @return*/@Select("select * from user")@Results(id="userMap",value = {@Result(id = true,property = "userId",column = "id"),@Result(property = "userName",column = "username"),@Result(property = "userAddress",column = "address"),@Result(property = "userSex",column = "sex"),@Result(property = "userBirthday",column = "birthday")})List<User> findAll();/*** 根据id查询用户* @param userId* @return*/@Select("select * from user where id=#{uid} ")@ResultMap(value="userMap")User findById(Integer userId);/*** 根据用户名称模糊查询* @param username* @return*/@Select("select * from user where username like #{username} ")@ResultMap(value="userMap")List<User> findUserByName(String username);}
第三步:测试:
AnnotationCRUDTest.java
@Testpublic void testFindAll(){List<User> list = userDao.findAll();for(User user:list){System.out.println(user);}}@Testpublic void testFindOne(){User user = userDao.findById(57);System.out.println(user);}@Testpublic void testFindByName(){List<User> users = userDao.findUserByName("%mybatis%");for(User user : users){System.out.println(user);}}
这里:
@Results注解
代替的是标签
该注解中可以使用单个@Result注解,也可以使用@Result集合
@Results({@Result(),@Result()})或@Results(@Result())
@Result注解
代替了
@Result 中 属性介绍:
id boolean类型,是否是主键,默认是false(非主键)
column 数据库的列名
property对象中需要装配的属性名
one 需要使用的@One注解(@Result(one=@One)()))
many 需要使用的@Many注解(@Result(many=@many)()))
@One注解(一对一)
代替了
@One注解属性介绍:
select 指定用来多表查询的sqlmapper
fetchType会覆盖全局的配置参数lazyLoadingEnabled。。
使用格式: @Result(column=" ",property="",one=@One(select=""))
@Many注解(一对多)
代替了
注意:聚集元素用来处理“一对多”的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList)但是注解中可以不定义;
使用格式: @Result(property="",column="",many=@Many(select=""))
@One(多对一和一对一场景)
添加User实体类及Account实体类
【需求】:查询账号信息,同时查询用户信息。
定义:Account.java实体类
public class Account implements Serializable{private Integer id;private Integer uid;private Double money;private User user;}
定义:User.java实体类
public class User implements Serializable{private Integer userId;private String userName;private String userAddress;private String userSex;private Date userBirthday;}
添加UserDao接口及AccountDao接口
AccountDao.java
public interface AccountDao {/*** 查询所有账号* @return*/@Select("select * from account")@Results(id="accountMap",value = {@Result(id = true,property = "id",column = "id"),@Result(property = "uid",column = "uid"),@Result(property = "money",column = "money"),@Result(property = "user",column = "uid",one=@One(select = "com.it.dao.UserDao.findById",fetchType = FetchType.LAZY))})List<Account> findAll();}
UserDao.java
// 查询所有@Select(value = "select * from user")@Results(id="userMap",value = {@Result(id = true,property = "userId",column ="id" ),@Result(property = "userName",column ="username" ),@Result(property = "userAddress",column ="address" ),@Result(property = "userSex",column ="sex" ),@Result(property = "userBirthday",column ="birthday" )})public List<User> findAll();/*** 根据id查询用户* @param userId* @return*/@Select("select * from user where id=#{uid} ")@ResultMap(value="userMap")User findById(Integer userId);
测试:AnnotationCRUDTest.java
private InputStream in;private SqlSessionFactory factory;private SqlSession session;private UserDao userDao;private AccountDao accountDao;@Beforepublic void init()throws Exception{in = Resources.getResourceAsStream("SqlMapConfig.xml");factory = new SqlSessionFactoryBuilder().build(in);session = factory.openSession();userDao = session.getMapper(UserDao.class);accountDao = session.getMapper(AccountDao.class);}@Testpublic void testFindAllAccount(){List<Account> list = accountDao.findAll();for(Account account:list){System.out.println(account);}}
测试一下:延迟检索和立即检索
在SqlMapConfig.xml中配置:
<settings><!--开启延迟检索策略--><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/></settings>
@Many(一对多和多对多)
【需求】:查询所有的用户,将所有用户所对应的账号信息集合查询出来
添加User实体类及Account实体类
第一步:定义User.java
public class User implements Serializable{private Integer userId;private String userName;private String userAddress;private String userSex;private Date userBirthday;List<Account> accounts;}
4.4.3.2添加UserDao接口及AccountDao接口
第二步:UserDao.java
/*** 查询所有用户* @return*/@Select("select * from user")@Results(id="userMap",value = {@Result(id = true,property = "userId",column = "id"),@Result(property = "userName",column = "username"),@Result(property = "userAddress",column = "address"),@Result(property = "userSex",column = "sex"),@Result(property = "userBirthday",column = "birthday"),@Result(property = "accounts",column = "id",many = @Many(select = "com.it.dao.AccountDao.findByUid",fetchType = FetchType.LAZY))})List<User> findAll();
@Many:相当于
select属性:代表将要执行的sql语句
fetchType属性:代表加载方式,一般如果要延迟加载都设置为LAZY的值
column = "id":表示拿主键作为参数,去Account表去查询。
AcctounDao.java
@Select(value = "select * from account where uid = #{uid}") List<Account> findByUid(Integer uid); 第三步:AnnotationCRUDTest.java
@Testpublic void testFindAll(){List<User> list = userDao.findAll();for(User user:list){System.out.println(user);System.out.println(user.getAccounts());}}
测试立即检索和延迟检索。
使用联合查询的sql语句
【需求】:查询所有的账号信息,同时将账号信息对应的用户信息也查询出来。(使用联合查询)
第一步:定义sql SELECT u.*,a.id AS aid,a.uid,a.money FROM account a,USER u WHERE a.uid = u.id
第二步:Account.java对象
public class Account implements Serializable {private Integer id;private Integer uid;private Double money;private User user;}
第三步:在AccountDao.java中定义方法,同时定义@Select注解和@Results注解
// 查询所有的账号,同时关联出所有的用户@Select(value = "SELECT u.*,a.id AS aid,a.uid,a.money FROM account a,USER u WHERE a.uid = u.id")@Results(id = "accountUserMap",value = {@Result(id = true,property = "id",column = "aid"),@Result(property = "uid",column = "uid"),@Result(property = "money",column = "money"),@Result(property = "user.userId",column = "id"),@Result(property = "user.userName",column = "username"),@Result(property = "user.userSex",column = "sex"),@Result(property = "user.userAddress",column = "address"),@Result(property = "user.userBirthday",column = "birthday"),})List<Account> findAllAccountUser2();
第四步:测试
// 查询账号,同时将账号下的用户信息也查询出来@Testpublic void testFindAccountUser2() throws Exception {List<Account> list = accountDao.findAllAccountUser2();for (Account account : list) {System.out.println(account);}}
使用注解配置二级缓存
测试类SecondLevelCacheTest .java:
/***/public class SecondLevelCacheTest {private InputStream in;private SqlSessionFactory factory;@Beforepublic void init()throws Exception{in = Resources.getResourceAsStream("SqlMapConfig.xml");factory = new SqlSessionFactoryBuilder().build(in);}@Afterpublic void destroy()throws Exception{in.close();}@Testpublic void testSecondLevelCache(){SqlSession session = factory.openSession();UserDao userDao = session.getMapper(UserDao.class);User user = userDao.findById(50);System.out.println(user);session.close();// 重新开启SqlSession session1 = factory.openSession();UserDao userDao1 = session1.getMapper(UserDao.class);User user1 = userDao1.findById(50);System.out.println(user1);session1.close();}}
添加二级缓存
第一步:配置SqlMapConfig.xml 
<settings><!--开启二级缓存--><setting name="cacheEnabled" value="true"/></settings>
第二步:配置UserDao.xml
@CacheNamespace(blocking = true) // 使用二级缓存public interface UserDao {}
再次测试,发现二级缓存可以使用。 