- 本文参考文档:《Mybatis第四天讲义.pdf》
- 本文代码项目:mybatis_day04_lazy、mybatis_day04_cache、mybatis_day04_annotation(路径:G:\idea_java_project\)
今日内容
1. Mybatis中的延迟加载
1)什么是延迟加载
2)什么是立即加载
2. Mybatis中的缓存
1)什么是缓存
2)为什么使用缓存
3)什么样的数据能使用缓存,什么样的数据不能使用
4)Mybatis中的一级缓存和二级缓存
3. Mybatis中的注解开发
1) 开发环境搭建
2) 单表CRUD操作(代理Dao方式)
3) 多表查询操作
4)缓存的配置
1、Mybatis 延迟加载
问题:在一对多中,当我们有一个用户,它有100个账户。
在查询用户的时候,要不要把关联的账户查出来?
在查询账户的时候,要不要把关联的用户查出来?
解析:
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。
在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。
什么是延迟加载:
在真正使用数据时才发起查询,不用的时候不查询。也叫按需加载(懒加载)
什么是立即加载:
不管用不用,只要一调用方法,马上发起查询。这种查询方法对服务器的压力较大,因为一次性要查询较多的信息。
在对应的四种表关系中:一对多,多对一,一对一,多对多,按照加载情况可以分为2种:
一对多,多对多:通常情况下我们都是采用延迟加载。(查询出来的其他信息太多,一般要使用其他信息时,才将这些内容加载出来)
多对一,一对一:通常情况下我们都是采用立即加载。(查询出来的其他信息只有一个,可以立即加载出来)
mybatis实现一对一(多对一)延迟加载(association )
前面的多对一(mybatis中也叫一对一)案例中,在查询账户的时候,同时会查询出来用户信息。我们想要实现查询账户的时候先不查询用户,而是等到需要的时候再延迟加载查询账户对应的用户信息。(这种多对一的情况一般不需要延迟加载,为了演示我们设置这种情况的延迟加载)
需求:前面实现立即加载,我们调用findAll:查询所有账户,并且带有账户所对应的用户信息,会立即将account表以及账户对应的user表中的数据全部查询出来。因此我们需要使用延迟加载,加载account的信息的时候,不会立即加载user的信息,而是等到有需求的时候再加载user。
步骤
1、延迟加载针对级联使用的,延迟加载的目的是减少内存的浪费和减轻系统负担。
什么是级联?
比如user表和role表有关联关系,有这样一条语句:查询uesr的同时将user的某一列数据作为参数一并查询role表符合条件的数据,mybatis里叫做级联。只要执行这条语句,就会将这两张表符合需求的信息一起加载出来。而懒加载只会加载uesr表的数据出来不加载role表的数据。
2、延迟加载resultMap的配置
<!-- 定义封装查询出来的 account信息,并指定延迟加载查询账户所对应用户信息所需要的方法 -->
<resultMap id="accountUserMap" type="account">
<!--首先,Account类中配置封装account表查询出来的内容-->
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 一对一的关系映射:定延迟加载查询账户所对应用户信息所需要的方法-->
<!--
1、同样适用<association>标签配置,我们这里不需要将user表的属性值封装到User类的属性中,因为我们这里不会立即查询user表的数据。
2、select属性指定的内容:查询用户的唯一标识。既我们想通过什么方法来查询用户信息findById方法)。也可以理解为:我们要调用的 select 方法的方法名。
column属性指定的内容:用户根据id查询时(findByID方法),所需要的参数的值。也可以理解为:我们要传递给 select 方法的参数
在UserDao中有一个根据id查询用户信息的方法:findById ,我们这里指定 select 属性指向这个方法(注意加上UserDao的全限定类名) ,
同时,前面查询的时候,column 属性的值是可以不写的,但是这里必须要写,因为 column="uid" 表示的是查询出来的account表中用户的uid属性值,
而uid属性对应user表中的id属性,我们的 select 标签中的 findById 方法需要依据这个uid,去查询前面查询出来的账户信息对应的用户信息!
3、首先,我们如果没有要求查询用户信息,则只会查询账户信息,延迟加载成功。一当我们要求查询账户对应的用户信息,就会触发 select 标签的
findById 方法,这个方法会依据查询出来的account表的uid属性值,去查询user表中对应的用户信息。
-->
<association property="user" column="uid" javaType="user" select="com.lkj.dao.UserDao.findById"></association>
</resultMap>
3、查询语句的配置
<!-- 查询所有账户,并且延迟加载查询带有账户所对应的用户信息 -->
<!--
1、既然是延迟加载,我们这里的SQL语句就只先查询账户account信息:SELECT * FROM account
而不是像前面一股脑将account对应的user用户信息也查询出来
-->
<select id="findAll" resultMap="accountUserMap">
SELECT * FROM account
</select>
----UserDao中根据根据id查询用户的方法
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user">
select * from user where id = #{uid}
</select>
4、想要真正实现延迟加载,还需要在SqlMapConfig.xml文件中配置(一般都使用如下配置方式):
<!--配置延迟加载-->
<settings>
<!--开启Mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
这两个属性的具体作用,见下面解析。
1)注意!!!标签的顺序,必须位于properties之后typeAliases之前。如果位置错乱会导致mybatis启动失败。
2)aggressiveLazyLoading 3.4.1(包含)前默认为true,之后默认为false。我们的版本是3.5.4,可以不设置,默认为false。
3)我们上面这样设置,会将全部的级联都设计延迟加载。如果不想将全部级联设置为延迟加载,association和collection有个fetchType属性可以覆盖全局的懒加载状态:eager表示这个级联不使用懒加载要立即加载,lazy表示使用懒加载。(当然也可以直接设置fetchType属性来设置延迟加载)
4)aggressiveLazyLoading是做什么么的?
它是控制具有延迟加载特性的对象的属性的加载情况的。true表示如果对具有延迟加载特性的对象的任意调用会导致这个对象的完整加载,false表示每种属性按照需要加载。(具体见下面测试2)
关于lazyLoadingEnabled与aggressiveLazyLoading2个属性的功能,可以参考文章:添加链接描述
测试1:延迟加载测试
1)先如上配置延迟加载的2个属性,执行下面的测试
/**
* 查询所有账户,并且带有账户所对应的用户信息
*/
@Test
public void testFindAll()
{
List<Account> list = accountDao.findAll();
for (Account account : list)
{
System.out.println("----------每个account的信息-----------");
System.out.println(account.getId()+"---"+account.getUid()+"---"+account.getMoney());
// System.out.println(account.getUser());
}
/*
我们这里不使用 System.out.println(account) 测试,因为打印account对象,会把account中的user属性也打印出来,
这样便会执行级联动作,既会执行查询user表的SQL语句。
如果我们仅仅是打印account中的id\uid\money 等属性,就不会打印user,不会执行查询user的SQL语句(视频中那样直接打印account不好)
*/
}
}
2)结果:如果我们没有获取user表的数据,就不会查询user表,实现延迟加载!
3)我们打印(获取)user表相关的信息。
/**
* 查询所有账户,并且带有账户所对应的用户信息
*/
@Test
public void testFindAll()
{
List<Account> list = accountDao.findAll();
for (Account account : list)
{
System.out.println("----------每个account的信息-----------");
System.out.println(account.getId()+"---"+account.getUid()+"---"+account.getMoney());
System.out.println(account.getUser());
}
}
}
4)结果
测试2:aggressiveLazyLoading测试。
在Account的配置中配置级联,那么称Account的实例为带有延迟加载属性的对象(下面的account属性具有延迟加载特性)。
1)将aggressiveLazyLoading设置为false
/**
* 查询所有账户,并且带有账户所对应的用户信息
*/
@Test
public void testFindAll()
{
List<Account> list = accountDao.findAll();
for (Account account : list)
{
System.out.println("----------每个account的信息-----------");
System.out.println(account.getId()+"---"+account.getUid()+"---"+account.getMoney());
// System.out.println(account.getUser());
}
}
结果:我们发现,查询user表的SQL没有执行,我们只调用account的属性,那么就不会调用user的属性。既即每种属性按需加载,不调用就不加载。
2)同样是上面的代码,我们将aggressiveLazyLoading设置为true,我们发现只查询出了account的信息,但是同样执行了对user表的查询。**既只要对这个类的任意操作,将完整加载整个类的所有属性,即执行级联的SQL语句。**我们只查询account的id、uid、money属性,但是会加载account的user属性,并执行级联的查询SQL的语句。
mybatis实现一对多延迟加载(collection)
需求:完成加载用户对象时,延迟加载查询该用户所拥有的账户信息。既需要的时候再查询用户对应的账户信息。
1、resultMap设置
<resultMap id="userAccountMap" type="user">
<!--首先是配置User对象原有属性的映射。信息从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>
<!--一对多的关系映射:配置延迟加载查询用户所对应账户信息所需要的方法-->
<!--
1、同样,前面配置的account表与Account类对应的信息不再需要。这里使用<collection>标签配置
2、select:查询账户的唯一标识。我们通过AccountDao的findAccountByUid方法来查询 user表对应的account表的信息。
column:根据uid查询account信息时(findAccountByUid方法),所需要的参数的值。也可以理解为:用于指定 select 属性的 sql 语句的参数来源,上面的参数来自于 user 的 id 列,所以就写成 id 这一个字段名了。
3、在AccountDao中有一个根据uid查询账户信息的方法:findAccountByUid ,我们这里指定 select 属性指向这个方法(注意加上AccountDao的全限定类名) ,
column="id" 表示的是查询出来的user表中用户的id属性值,而id属性对应account表的uid属性,我们的findAccountByUid 方法需要依据
这个user表id对应的account表中uid的值(user表的id = account表的uid),去查询前面查询出来的用户对应的账户信息。
-->
<collection property="accounts" ofType="account" select="com.lkj.dao.AccountDao.findAccountByUid" column="id"> </collection>
</resultMap>
3、查询语句
<!-- 询所有用户,延迟加载获取该用户下的所有账户信息 -->
<select id="findAll" resultMap="userAccountMap">
select * from user
</select>
4、级联的AccountDao中根据uid查询账户信息的方法(视频中源码的方法错误,没有写 findAccountByUid 方法的参数,但是不知道为啥,也可以查询出来,很奇怪)
<!-- 根据账户uid查询账户列表 -->
<select id="findAccountByUid" resultType="account" parameterType="INT">
select * from account where uid = #{uid}
</select>
测试:只查询user表的信息,不查询user对应的account。
public void testFindAll()
{
List<User> list = userDao.findAll();
for (User user : list)
{
System.out.println("------每个用户信息------");
System.out.println(user.getUsername());
// System.out.println(user.getAccounts());
}
}
结果:
查询user表的account属性
public void testFindAll()
{
List<User> list = userDao.findAll();
for (User user : list)
{
System.out.println("------每个用户信息------");
System.out.println(user.getUsername());
System.out.println(user.getAccounts());
}
}
结果:
这里面的思想,就是在使用的时候,去调用对方配置文件的一个配置方法来实现级联查询的功能。如UserDao中的findAll方法,在查询用户信息后,要查询用户对应的账户信息,就会调用AccountDao中的findAccountByUid方法来完成账户信息的查询。
2、mybatis中的缓存
什么是缓存?
存在于内存中的临时数据。
为什么使用缓存?
减少和数据库的交互次数,提高执行效率。
什么样的数据能使用缓存,什么样的数据不能使用适用于缓存:
1) 经常查询并且不经常改变的;
2) 数据的正确与否对最终结果影响不大的。(缓存中的数据可能与数据库的数据不同步)
不适用于缓存:经常改变的数据
1) 数据的正确与否对最终结果影响很大的。一当这种数据缓存的数据与数据库的数据不同步,那就会引起很大的问题!
例如:商品的库存,银行的汇率,股市的牌价
Mybatis中的一级缓存和二级缓存。(参考项目 mybatis_day04_cache 的代码 以及视频6的解析)
一级缓存
一级缓存:它指的是Mybatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了。
下面我们通过测试证明以及缓存的存在。
测试1:我们先根据id查询User对象(其他的设置参考 项目 mybatis_day04_cache 的代码 )
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache()
{
User user1 = userDao.findById(41);
System.out.println(user1);
User user2 = userDao.findById(41);
System.out.println(user2);
//我们在User类中不重写toString方法,而是直接打印对象的地址
System.out.println(user1 == user2);
}
测试2:关闭SqlSession,清除一级缓存,再重新获取SqlSession。
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache()
{
User user1 = userDao.findById(41);
System.out.println(user1);
//先关闭前面的SqlSession
sqlSession.close();//当SqlSession对象关闭,它里面存储的一级缓存也消失
//再次获取SqlSession
sqlSession = factory.openSession();
userDao = sqlSession.getMapper(UserDao.class);
User user2 = userDao.findById(41);
System.out.println(user2);
//我们在User类中不重写toString方法,而是直接打印对象的地址
System.out.println(user1 == user2);
}
我们执行如下代码,也可以清空一级缓存。
public void testFirstLevelCache()
{
User user1 = userDao.findById(41);
System.out.println(user1);
//先关闭前面的SqlSession
// sqlSession.close();//当SqlSession对象关闭,它里面存储的一级缓存也消失
// //再次获取SqlSession
// sqlSession = factory.openSession();
// userDao = sqlSession.getMapper(UserDao.class);
sqlSession.clearCache();//此方法也可以清空缓存
User user2 = userDao.findById(41);
System.out.println(user2);
//我们在User类中不重写toString方法,而是直接打印对象的地址
System.out.println(user1 == user2);
}
我们关闭SqlSession,或者使用SqlSession的clearCache方法,都可以清空SqlSession中的一级缓存。
下面对一级缓存在数据库数据发生改变的时候如何进行同步的问题进行分析。(具体代码见 项目mybatis_day04_cache 与视频7 分析,我们这里只进行测试)。
测试:测试代码如下
/**
* 测试缓存的同步
*/
@Test
public void testClearlCache(){
//1.根据id查询用户
User user1 = userDao.findById(41);
System.out.println(user1);
//2.更新用户信息
user1.setUsername("update user clear cache");
user1.setAddress("北京市海淀区");
userDao.updateUser(user1);
//3.再次查询id为41的用户
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
首先,我们先不更新用户信息,而是查询2次id=41的用户信息。
其次,我们添加用户信息的更新
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除, commit(), close(),clearCache()等方法时,就会清空一级缓存。 因此我们第二次查询id=41的User对象的时候,由于数据库更新,前面的以及缓存被清除,无法在缓存中读取到相应的id=41的User对象的数据,就会去数据库查询,执行SQL查询方法。
二级缓存
二级缓存:它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
测试二级缓存
二级缓存的使用步骤:(这里的代码参考项目 mybatis_day04_cache)
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
<!--这里 cacheEnabled 默认是true,不配置也可以-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
第二步:让当前的映射文件支持二级缓存(在UserDao.xml中配置)
如下,在UserDao.xml文件的<mapper>标签的开头创建<cache/>标签
<!--使得当前的UserDao支持二级缓存-->
<cache/>
第三步:让当前的操作支持二级缓存(在select标签中配置)
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
</select>
如上,在“findById”方法的标签中设置属性useCache="true"
测试代码如下:
public void testFirstLevelCache()
{
/*
1、注意,这里使用同一个SqlSessionFactory生成SqlSession,这些SqlSession查询数据库的数据会存储到二级缓存
2、我们关闭以及缓存是为了避免SqlSession中的以及缓存对二级缓存的演示产生影响
*/
SqlSession sqlSession1 = factory.openSession();
UserDao dao1 = sqlSession1.getMapper(UserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();//关闭一级缓存
SqlSession sqlSession2 = factory.openSession();
UserDao dao2 = sqlSession2.getMapper(UserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();//关闭一级缓存
//我们在User类中不重写toString方法,而是直接打印对象的地址
System.out.println(user1 == user2);
}
虽然第二次获取id=41的User对象查询缓存,但是前后2次User对象不同。这是因为我们二级缓存中存放的是数据,而不是对象。第一次查询出来的User对象(id=41),存放到二级缓存中是散装的数据:“id”=41,username = “…”,sex="…" ,第二次查询,二级缓存就会创建一个新的User对象,并将它里面的散装的数据重新封装到User对象中。既二级缓存虽然没有查询数据库,但是会创建一个新的User对象,这样前后2个对象就不是同一个。
当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
3、mybatis注解开发
首先明确,注解替换的是每个Dao的配置文件——dao.xml中的配置信息,既我们可以不使用Dao.xml文件,但是主配置文件SqlMapConfig.xml还是需要的。
3.1 环境搭建
这部分参考视频9的分析,其实我们之前已经搭建过。
测试与注意事项(视频10),注意事项见视频10–5.40
1)用注解为什么可以实现dao.xml文件的功能(在之前自定义Mybatis的时候已经说过!)
2)当我们在主配置文件使用 标签为dao下的文件指定别名的时候,
<package name="com.lkj.dao"></package>
这这个时候,有注解会用注解,而有dao.xml会使用dao.xml。但是我们导入UserDao.xml后,注解与dao.xml都要,这个时候运行就会出错。
这如果我们使用标签的class属性指定注解开发指向的dao,同时项目中存在dao.xml文件
<mapper class="com.lkj.dao.UserDao"></mapper>
我们发现,虽然没有使用dao.xml文件,但是还是会出错。
Mybatis细节:如果我们使用注解开发,但是只要我们的配置文件路径下同时包含dao.xml文件,不管我们在主配置文件中有没有使用dao.xml(有没有在主配置文件中指定dao.xml的映射),系统都会报错。
当我们使用注解开发的时候,就不要再使用dao.xml文件,或者将dao.xml移动到正确的文件夹之外,dao.xml就不会生效。
3.2 注解开发——单表CRUD
参考视频11以及项目mybatis_day04_annotation,这部分较为简单。
3.3 注解开发——建立实体类属性和数据库中标的对应关系
将User实体类中的属性值修改为与user表中的属性名不同。
1、这个时候,我们调用查询所有用户的方法findAll(),我们数据库user表的属性与实体类User的属性对应不上,因此,最后查询出来的数据无法封装到User表的属性中,如下图1
2、这个时候,我们还是有2中方法。第一种是给user表的属性其一个与User表属性名对应相同的别名,但是这样所有查询的语句都要起别名,明显不合适。
我们使用@Results 与 @Result 2个注解来配置实体类属性与数据库表属性的对应关系:
@Select(value = "select * from user")
/*
注意点:
1、外面的@Results 注解有2个属性 String id() default ""、 Result[] value() default {} ,
第一个id是设置resultMap的名字,id = "userMap";
第二个属性value是一个Result类型的数组,应该设置为: value={Result属性值1,Result属性值2,...} (注意,注解的数组值使用 {} 包围起来,不是[])
2、Result类型也是一个注解:@Result,里面3个重要的属性为: boolean id() default false, String column() default "", String property() default ""
其中,id用来设定外面配置的属性是否是id(默认是false,不为id);proper用来设置实体类属性,column设置数据库表的属性
*/
@Results(id = "usetMap" , value = {
@Result(id = true, property = "userId" , column = "id"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday")
})
List<User> findAll();
配置映射后,数据全部封装成功,如下图2。
之后,我们其他的查询方法如果想使用这里的映射,可以用@ResultMap 注解来指定我们要使用的映射,这样就不需要在每一个方法都定义user表与实体类User的映射。
@Select("select * from user where id=#{uid}")
/*
@ResultMap注解有一个属性:String[] value(),我们通过value指定我们要使用的映射,这个映射必须以及定义过,且已经命名。
*/
@ResultMap(value = {"userMap"}) //value只有一个值,且只有value一个属性,也可以写为:@ResultMap("userMap") 或者 @ResultMap(value = "userMap")
User findById(Integer userId);
findById方法可以正常实现数据的封装,如下图3
3.4 注解开发—— 一对一查询配置
account表对user表的一对一操作(一个账户对应一个用户),我们要查询所有账户,并且获取每个账户所属的用户信息。
1、我们在AccountDao有查询所有账户的方法findAll,同时还要获取到当前账户的所属用户信息。最后查询出来的数据封装到Account类中。
2、我们在从表account表对应的实体类Account中,创建主表user的实体类User类的对象引用,并创建getter与setter方法。
//多对一(mybatis中称之为一对一)的映射:一个账户只能属于一个用户。创建一个User类型的对象
private User user;
3、我们在AccountDao的findAll()方法中,进行如下设置:
@Select("select * from account ")
//我们下面进行 一对一的关系映射:配置查询User表的相关信息并封装到Account实体类的user属性。
@Results(id = "accountMap" , value = {
//首先,封装account表的信息
@Result(id = true , property = "id" , column = "id"),
@Result(property = "uid" , column = "uid"),
@Result(property = "money" , column = "money"),
/*
@Result 注解中还有一个 One one() default @One属性,这个属性代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
既 one 属性是像 <assocation>标签 标签一样,用来在多对一(mybatis一对一)中封装关联主表的属性的。one的默认值是注解 @One.
我们注意到,@One 注解有2个属性:String select() default ""、FetchType fetchType() default FetchType.DEFAULT。
1、@One 并没有像@Result 注解中的property、column、id等属性,不能像之前 dao.xml配置一样,在<assocation>标签中配置封装user的内容,
而是要在这里像前面的延迟加载一样操作。
由于我们这里指定@Result column="uid" ,account的uid 与 user的id相同,使用 select 指定UserDao 中可以根据id 查询用户信息的方法findById ,
会直接根据这里的“uid” 属性值查询user表,并将查询出来的数据封装到Account对象的user属性。
注意:
1)查询出来的user表的数据是查询出来的账户信息对应的用户信息;
2)select指定方法的时候,使用: dao全限定类名+方法名
2、另外,fetchType属性的属性值有3个:LAZY(延迟加载), EAGER(立即加载), DEFAULT(一对多默认延迟,多对一默认立即)
3、虽然User类中的属性值与user表的属性值不同,我们使用 findById 方法,在将查询出来的账户信息对应的用户信息封装到user中的时候不会出错,
因为我们在UserDao的findById 方法中已经指定user表与User类属性的映射,我们findById方法查询user表数据后,
会根据映射的User类的名字,再将数据封装到Account的user属性。
4、前面延迟加载的做法:
<association property="user" column="uid" javaType="user" select="com.lkj.dao.UserDao.findById"></association>
5、使用<assocation>标签直接封装user内容的做法
<association property="user" column="uid" javaType="user">
<!--里面写User类属性与user表属性的对应-->
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</association>
*/
//property="user" ,我们要将查询出来的账户信息对应的用户信息封装到Account的user属性
//column="uid" , 上面说过,select中的方法根据 account 表的这个属性值,去查询user表
@Result(property = "user" , column = "uid" , one=@One(select = "com.lkj.dao.UserDao.findById" , fetchType = FetchType.LAZY))
})
List<Account> findAll();
测试:
@Test
public void testFindAll()
{
List<Account> accounts = accountDao.findAll();
for (Account account : accounts)
{
System.out.println("----每个账户的信息-----");
System.out.println(account);
System.out.println(account.getUser());
}
}
测试延迟加载,我们不打印User的信息(不获取Account的user属性),而只是打印Account的money属性。
@Test
public void testFindAll()
{
List<Account> accounts = accountDao.findAll();
for (Account account : accounts)
{
System.out.println("----每个账户的信息-----");
System.out.println(account.getMoney());
// System.out.println(account.getUser());
}
}
3.5 注解开发—— 一对多查询配置
查询所有用户信息及用户关联的账户信息。
1、在UserDao下有一个方法:
//查询所有用户,同时获取该用户下的所有账户信息
List<User> findAll();
2、我们想通过主表查询到对应从表的信息(一对多关系映射),此时主表实体应该包含从表实体的集合引用
//一对多关系映射:一个用户对应多个账户。
private List<Account> account;
注意区别于从表到主表查询的一对一,一对一从表实体应该包含一个主表实体的对象引用。(因为User可能对应多个Account,而Account只能对应一个User)
3、我们要查询用户信息,以及根据用户id对应的账户uid,查询账户信息,要在UserDao中调用子查询,使用AccountDao中的findByUid()方法,查询出用户对应的账户信息:
/**
* 根据用户id(既账户uid)查询账户信息
* @param uid
* @return
*/
//这里Account类的属性与account表的属性相同,因此我们不需要设置映射,account表查询出来的数据可以直接封装到Account对象
@Select("select * from account where uid = #{uid}")
List<Account> findAccountByUid(Integer uid);
4、下面,我们在UserDao的findAll()方法中,进行子查询(级联查询)的配置,先查询用户信息,根据用户的id(账户的uid)查询用户对应的账户信息。
/**
* 查询所有用户
*
* @return 在mybatis中针对, CRUD一共有四个注解
* @Select @Insert @Update @Delete
*/
@Select(value = "select * from user")
@Results(id = "userMap" , value = {
@Result(id = true, property = "userId" , column = "id"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
/*
子查询(级联查询)的配置,先查询用户信息,根据用户的id(账户的uid),查询用户对应的账户的信息。
1、@Result 注解中还有一个 Many many() default @Many 属性,与前面的one属性类似,不管这个属性是设置一对多查询,
one属性代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
2、many属性的默认值是注解@Many,这个注解有2个属性:String select() default ""、FetchType fetchType() default FetchType.DEFAULT
2个属性的作用与前面@One2个属性作用相同。
3、
延迟加载的做法
<collection property="accounts" ofType="account" select="com.lkj.dao.AccountDao.findAccountByUid" column="id"> </collection>
使用<collection>标签直接封装accounts内容的做法
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
*/
//property = "accounts":表示查询出来的账户信息封装到User表的accounts属性
//column = "id":表示根据user表的id属性(对象account表的uid属性)去查询用户对应的账户信息
//这里设置延迟加载:fetchType = FetchType.LAZY;select指定的方法findByUid使用全限定类名
@Result(property = "accounts" , column = "id" , many = @Many(select = "com.lkj.dao.AccountDao.findAccountByUid" , fetchType = FetchType.LAZY))
})
List<User> findAll();
测试:如下,成功查询出用户对应的账户信息
@Test
public void testFindAll()
{
List<User> users = userDao.findAll();
for(User user : users){
System.out.println("---每个用户的信息----");
System.out.println(user);
System.out.println(user.getAccounts());
}
}
测试延迟加载:不会查询用户对应的账户信息。
@Test
public void testFindAll()
{
List<User> users = userDao.findAll();
for(User user : users){
System.out.println("---每个用户的信息----");
System.out.println(user.getUserName());
// System.out.println(user.getAccounts());
}
}
3.6 注解开发——使用二级缓存
一级缓存是在SqlSession中设置的,我们关闭SqlSession,或者使用SqlSession的clearCache方法,或者调用增删改方法,都可以清空SqlSession中的一级缓存。因此,注解开发以及缓存的使用与dao.xml开发中以及缓存的使用相同。
而dao.xml的开发中,二级缓存需要配置
1、SqlMapConfig.xml中配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2、在UserDao.xml中配置
在UserDao.xml文件的<mapper>标签的开头创建<cache/>标签
<!--使得当前的UserDao支持二级缓存-->
<cache/>
3、 第三步:让当前的操作支持二级缓存(在select标签中配置)
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
</select>
如上,在“findById”方法的标签中设置属性useCache="true"
在注解开发中,设置二级缓存,
测试:先不配置二级缓存
@Test
public void testFindById()
{
SqlSession sqlSession1 = factory.openSession();
UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
User user1 = userDao1.findById(53);
System.out.println(user1);
sqlSession1.close();//关闭一级缓存
SqlSession sqlSession2 = factory.openSession();
UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
User user2 = userDao2.findById(53);
System.out.println(user2);
sqlSession2.close();//关闭一级缓存
System.out.println(user1 == user2);
}
随后我们配置二级缓存
1、SqlMapConfig.xml中配置(可以不设置,默认开启)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2、在要开启二级缓存的dao的接口前面,设置如下,这样就开启二级缓存
@CacheNamespace(blocking = true)
public interface UserDao{.....}
测试:代码与上面相同