今日内容
1. Mybatis中的延迟加载
什么是延迟加载
什么是立即加载
2. Mybatis中的缓存
什么是缓存
为什么使用缓存
什么样的数据能使用缓存,什么样的数据不能使用缓存
mybatis中的一级缓存和二级缓存
3. Mybatis中的注解开发
环境搭建
多表CRUD操作(代理Dao方式)
多表查询操作
缓存的配置
1 Mybatis中的延迟加载
1.1 延迟加载概述
问题:在一对多情况中,当我们有一个用户,他有100个账户。
在查询用户:的时候,要不要把关联的账户查出老?
在查询账户的时候,要不要把关联的用户查出来?
1.查询用户:要不要把关联的账户查出老?
* 我们要做的功能是查询用户(user),但是由于我们用户里面关联了一个账户集合(accounts),
且这个用户有100个账户,我们一查用户就会把这100个账户也查出来。显然这对内存是一个非常大的
开销。
所以我们如果用不到这100个账户信息时,是完全不应该将它查询出来的。
既然没有查出来,拿到用的时候又该怎么办呢?
* 最好的解决办法:延迟加载
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询。
---------------------延迟加载
2.查询账户:要不要把关联的用户查出来?
通常情况下,在查询账户时,账户的所属用户信息,应该是随着账户查询时一起查询出来。
---------------------立即加载
3.延迟加载
在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
4.立即加载
不管用不用,只要一调用方法,马上发起查询加载。
5.在对应的四种表关系中:
一对多,多对多
关联的对象是多:延迟加载
多对一,一对一
关联的对象是一:立即加载
1.2 延迟加载案例需求
需求:
查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可
满足要求,当需要时再去查询关联的用户(User)信息。
nyvatis第三天实现的多表操作时,我们使用了resultMap来实现一对一,一对多多关系的操作。主要
是通过association,collection实现一对一及一对多映射。而且association,collection具备延迟
功能。
注意:
既然是延迟加载,那么我们查询出来的结果就应该是查询账户(Account)的信息,再按需查询
用户(User)的信息。
sql:select * from account
而不是:select u.*, a.id as aid, a.uid, a.money from account a, user u where u.id = a.uid;
1.3 使用association实现延迟加载
1.3.1 账户的持久层DAO接口
```
public interface IAccountDao {
/**
* 查询所有账户
* @return
*/
List<Account> findAll();
}
```
1.3.2 账户的持久层映射文件(一)
```
<mapper namespace="com.itheima.Dao.IAccountDao">
<!--定义封装account和user的resultMap-->
<!--配置查询结果的列名和实体类的属性名的对应关系-->
<resultMap id="accountUserMap" type="com.itheima.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="User"
select="com.itheima.Dao.IUserDao.findById">
</association>
</resultMap>
</mapper>
```
1.3.3 association 映射分析
```
<association property="user" column="uid" javaType="User" select="com.itheima.Dao.IUserDao.findById">
```
* 1首先association是用来映射Account实体类中的引用实体属性:private User user;
```
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
}
```
* 2 asscociation的各个属性分析
* 3select属性分析:
select="com.itheima.Dao.IUserDao.findById"
填写我们要调用的select映射(标签)的id。其实是namespace+id。根据这个我们能在所有的映射文件
XxxDao.xml找到这个唯一的select,进行调用。
此处是IUserDao.xml中的select
```
<select id="findById" parameterType="INT" resultType="User">
select * from user where id = #{id};
</select>
```
即:com.itheima.Dao.IUserDao.findById(column : id) column就是association的column属性
* 4很重要:配置好了之后,到底是怎么延迟加载呢?
lazyLoadingEnabled: 注意这是特别针对关联对象的延迟加载
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
aggressiveLazyLoading:
我们要关闭这个积极加载关联对象属性,关掉后就成了按需加载。
##### 1.3.4 账户的持久层映射文件(二)
```
<resultMap id="accountUserMap" type="com.itheima.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="User"
select="com.itheima.Dao.IUserDao.findById">
</association>
</resultMap>
```
使用配置好的<resultMap> <association>...</association> </resultMap>
resultMap="accountUserMap":
```
<!--查询所有方法-->
<select id="findAll" resultMap="accountUserMap">
select * from account
</select>
```
注意:此时如果没有开启lazyLoadingEnabled和aggressiveLazyLoading属性时,会将
select * from account
select * from user where id = #{id};
都执行了。
因为我们把resultMap="accountUserMap":加到了<select id="findAll" resultMap="accountUserMap">
里面,显然是要调用这个映射且执行的。
1.3.5 最后配置lazyLoadingEnabled和aggressiveLazyLoading属性
* 主配置文件:SqlMapConfig.xml
```
<settings>
<!--打开延迟加载的开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--将积极加载改为按需加载-->
<!--在 3.4.1 及之前的版本中默认为 true,我们的mybatis的依赖是3.4.5,所以这个不为之也可以,此处为了演示-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
```
* 将Test中的打印User删掉(不然会被认为是按需)
```
for (Account account : accounts) {
System.out.println("----------------------");
System.out.println(account);
System.out.println(account.getUser());
}
```
控制台结果:
1.4 使用collection实现延迟加载
1.4.1 在User实体类中加入List属性
```
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;
}
}
```
1.4.2 编写用户和账户持久层接口的方法
```
public interface IUserDao {
/**
* 查询所有用户,同时获取用户下所有账户的信息
* @return
*/
List<User> findAll();
}
public interface IAccountDao {
/**
* 根据用户id查询账户信息
* @param uid
* @return
*/
List<Account> findAccountByUid(Integer uid);
}
```
1.4.3 编写用户持久层映射配置
```
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.itheima.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<!--配置 user对象中account集合的映射-->
<!--注意:findAccountByUid需要的参数是user中的id-->
<collection property="accounts" ofType="account" select="com.itheima.Dao.IAccountDao.findAccountByUid" column="id">
</collection>
</resultMap>
<!--查询所有方法-->
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM USER
</select>
```
<collection>:用于加载关联的集合对象
select属性:
用于指定查询account列表的sql语句,所以填的是该sql的映射id
column属性
用于指定select属性的sql语句的参数来源,该例是来自于user的id列
1.4.4 编写账户持久层映射配置
```
<!--根据用户id查询账户列表-->
<select id="findAccountByUid" resultType="account">
<!--
注意这里我们没有parameterType但是仍然写了#{uid}
所以这个uid不是来自parameterType而是来自IUserDao.xml中调用该映射是穿过来的参数column="id",
而且这个是user中的id,对应着account中的uid字段。
-->
select * from account where uid = #{uid}
</select>
```
1.4.5 测试只加载用户信息
```
/**
* 测试查询所有
*/
@Test
public void testFindAll(){
List<User> users = usertDao.findAll();
}
```
1.4.6 测试结果
发现只执行了查询用户的sql语句,没有加载Account账户信息。
2 Mybatis中的缓存
2.1 缓存概述
1. 什么是缓存:
存在于内存中的临时数据,下次使用的时候不用再去数据库查询,减少和数据库交互的次数,从而能够快速的
将数据获取出来。
2. 为什么使用缓存
减少和数据库的交互次数,提高执行效率
3. 什么样的数据能使用缓存,什么样的数据不能使用
适用于缓存的:
经常查询的并且不经常改变的。
数据的正确与否对最终结果不大的。
不适用于缓存的:
经常改变的,数据的正确与否对最终的结果影响很大的。
例如:商品的库存,银行的汇率,股市的牌价
可能出现的问题:导致缓存和数据库中的数据不同步的问题。
概述:像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数
从而提高性能
2.2 Mybatis中的一级缓存
一级缓存:SqlSession级别的缓存。
它指的是Mybatis中的SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
查询是否有,有的话直接拿出来用。
当sqlsessoin对象消失时,mybatis的一级缓存也就消失了。
测试代码1:
* 第一次是sql查询,第二次是从缓存中取的数据,两次获取的数据是一样的
```
public void testFirstLevelCache(){
//第一次是sql查询
User user1 = usertDao.findById(41);
System.out.println(user1);
//第二次是从缓存中取的数据
User user2 = usertDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
```
测试代码2:
* 因为中间sqlSession关闭了,两次获取数据都是通过sql查询的。那么查询获得的不是同一个对象
```
public void testFirstLevelCache(){
//第一次是sql查询
User user1 = usertDao.findById(41);
System.out.println(user1);
//关闭sqlSession
sqlSession.close();
//再次获取sqlSession对象
sqlSession = factory.openSession();
//重新获取代理对象
usertDao = sqlSession.getMapper(IUserDao.class);*/
//第二次是从缓存中取的数据
User user2 = usertDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
```
测试代码3:
sqlSession.clearCache(); 可以清空sqlSession中的缓存。
```
public void testFirstLevelCache(){
//第一次是sql查询
User user1 = usertDao.findById(41);
System.out.println(user1);
//清空缓存
sqlSession.clearCache();
//第二次是从缓存中取的数据
User user2 = usertDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
```
2.2.2 触发清空Mybatis中的一级缓存
为了防止缓存中的数据和数据库中的数据不一致。
一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit().close()等方法时,
就会清空一级缓存
2.3 Mybatis中的二级缓存
二级缓存:
它指的是Mybatis中的SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession
共享其缓存。
二级缓存的使用步骤:
1.让Mybatis矿建二级缓存(在SqlMapConfig.xml中配置)
```
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
```
2.让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
3.让当前的操作支持二级缓存(在select标签中配置)
*测试代码
*结果:
发现第二次查找是从二级缓存中得到的数据,但是为什么输出结果是false呢?
因为二级缓存中存放的内容,是数据而不是对象。
谁要拿这个数据,就会创建一个新的实体类对象(和里面的数据对应的--这里是user),把数据
填充到对象中。
所以虽然没有发起新的查询,但是创建了新的对象,这样的话这两个对象就不是同一个了。
3 Mybatis中的注解开发
3.1 概述
Mybatis也可以使用注解开发,这样我们就可以减少编写Mapper映射文件了(IUserDao.xml)
@Select一个注解就干完了IUserDao.xml的所有事情:一个@Select注解就拿到了所有的IUserDao.xml信息。
@Select注解在findAll()方法上,我们可以得到方法对象
找到这个方法,那么就可以同事找到这个方法声明的类,以及它的全限定类名。
@Select注解的value包含着Sql语句。
返回值类型:List<User> findAll(); 就是<User>
所以说:一个@Select注解就拿到了所有的IUserDao.xml信息。
3.2 存在IUserDao.xml配置文件时,再用注解
会报错:
Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException:
### Error building SqlSession.
### The error may exist in com/itheima/dao/IUserDao.xml
提示我们:存在IUserDao.xml配置文件
小细节:只要你使用了注解开发,同时你的配置文件下包含了对应的IUserDao.xml,就会报错。
他发现你用了注解,又用了IUserDao.xml,mybatis不知道该以谁为准。
如果一个dao用注解,那么这个dao就不会用IUserDao.xml
所以我们要么把IUserDao.xml移到不相关的配置文件,或者删掉。
即我们使用注解开发时,不能在同一个dao文件下使用IUserDao.xml。
3.3 如果实体类的属性名和数据库表中的列名没有一一对应
3.3.1 代码演示
* User类
```
@Data
public class User implements Serializable {
/**
* 由于属性名和数据库的列名没有一一对应,所以只能封装uerName(userName对应上了)
*/
private Integer userId;
private String userName;
private String userAddress;
private String userSex;
private Date userBirthday;
}
```
* IUserDao
```
@Select("select * from user")
List<User> findAll();
```
* 查询结果
3.3.2 解决办法
1. 在Sql语句中起别名
显然不仅仅调用findAll()方法时要起别名,调用findByiD()方法也要起别名。
因为起别名只作用在一个SQL,一个方法上,不太好。
2. @Results注解
```
@Select("select * from user")
@Results(value={
@Result(id=true,column="id",property = "userId"),
@Result(column="username",property = "userName"),
@Result(column="sex",property = "userSex"),
@Result(column="address",property = "userAddress"),
@Result(column="birthday",property : = "userBirthday"),
}, id="userMap")
List<User> findAll();
```
@Results 注解是一个 @Result 注解数组,
@Result注解在这里用到3个值:
id:默认值是false。 是不是主键,id列
column :数据库表的列名
property : 实体类的属性名
@Results :
id : 为 @Results指定一个唯一id值
3. 在其他方法处也可以引用这个注解:
注意:@ResultMap()的value是一个数组:
所以正确的写法:@ResultMap(value={"userMap"})
如果只有一个属性value,可以省略
如果value只有一个值:{}可以省略
@ResultMap("userMap")
4 Mybatis中多表查询的注解开发
4.1 一对一查询:一个账户对应一个用户
明确:一对一查询:积极加载
* Account实体类:
```
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
```
* 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="com.itheima.dao.IUserDao.findById", fetchType= FetchType.EAGER)),
})
List<Account> findAll();
```
one=@One(select="com.itheima.dao.IUserDao.findById", fetchType= FetchType.EAGER):
one属性:是@Result中的属性,表示一对一关系,值是@One注解
@One注解:两个属性:
select:用于指定我们要调用的select映射信息
fetchType:一对一关系中,你所指定的加载方式(LAZY,EAGER,DEFAULT)
4.2 一对多查询:一个用户对应一个账户
many=@Many(select="", fetchType=FETCHTYPE.LAZY)
IUserDao:
IAccountDao;
4.3 缓存的配置
一级缓存无需配置,默认就有。
二级缓存开启:
*1 SqlMapConfig.xml:
*2 测试代码:
*3 因为是IUserDao的代理对象:所以在IUserDao在配置一下二级缓存
* 开启二级缓存前的测试结果:
* 开启二级缓存后的测试结果: