mybatis的缓存与注解开发
mybatis的缓存机制
mybatis的懒加载机制
mybatis的注解开发
mybatis的注解开发-crud操作
mybatis的注解开发-一对一关联查询
mybatis的注解开发-一对多关联查询
mybatis的缓存主要有基于SqlSession的一级缓存和基于SqlSessionFactory的二级缓存,首先我们先来看一下mybatis的一级缓存。
mybatis的一级缓存
<select id="findUserById" parameterType="int" resultType="user">
select * from user where id=#{id}
</select>
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
User findUserById(Integer id);
//测试代码
public class Test2 {
private SqlSession sqlSession;
private UserDao userDao;
private AccountDao accountDao;
private RoleDao roleDao;
private InputStream input;
@BeforeEach
public void init() {
try {
input = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().
build(input);
sqlSession = build.openSession(true);
userDao = sqlSession.getMapper(UserDao.class);
accountDao = sqlSession.getMapper(AccountDao.class);
roleDao = sqlSession.getMapper(RoleDao.class);
} catch (IOException e) {
e.printStackTrace();
}
}
@AfterEach
public void destroy() {
try {
sqlSession.close();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void findByUserId() {
//调用两次查询语句,如果不存在缓存的话,那么一定是执行
//两次sql语句的查询操作,下面我们来看看结果来简单分析一下
System.out.println(userDao.findUserById(45));
System.out.println(userDao.findUserById(45));
}
}
我们可以看到这里调用了两次查询操作,但是只发送了一次查询的sql语句操作,说明在SqlSession级别的确是存在缓存的,接下来我们修改一下查询操作,先调用SqlSession的clearCache方法再去查询。
@Test
public void findByUserId() {
System.out.println(userDao.findUserById(45));
sqlSession.clearCache();
System.out.println(userDao.findUserById(45));
}
在我们调用了clear操作后,同样的查询语句这次发送了两条sql语句,说明session级别的缓存失效了。
一级缓存失效的几点原因
- 1.SqlSession调用了close方法,然后从SqlSessionFactory获取了一个新的SqlSession,那么原本的缓存自然就失效了
- 2.SqlSession调用了clearCache方法,清空了session中的缓存
- 3.缓存中的数据与数据库中的数据不一致了,那么什么情况下会导致缓存中的数据与数据库中的数据会不一致呢,当我们调用了数据的增、删、改操作以后。当我们使用了insert、delete、update方法后SqlSession会主动清空它所缓存的数据。
mybatis的二级缓存
mybatis的二级缓存是基于SqlSessionFactory级别的缓存,简单来说就是由同一个SqlSessionFactory创建的SqlSession可以获取到SqlSessionFactory中的缓存的数据,在SqlSessionFactory中缓存的数据不是pojo实体类对象,而是key-value的map数据。
要开启SqlSessionFactory的缓存需要一些配置,默认是不开启的,这个也是很自然,因为如果每次查询都要缓存,当然就很消耗内存了。
开启二级缓存的步骤:
1.在mybatis的配置文件mybatis-config.xml中配置支持二级缓存;
<settings>
<!--开启二级缓存的使用-->
<setting name="cacheEnabled" value="true"/>
</settings>
2.让当前的映射文件支持二级缓存(在映射文件中配置)
<cache/>
3.让当前的操作支持二级缓存(指定操作开启二级缓存)
<select id="findUserById" parameterType="int" resultType="user" useCache="true">
select * from user where id=#{id}
</select>
查询结果:
但是实际上mybatis的缓存其实是比较鸡肋的,因为我们通常做开发都是基于spring-framework的ioc实现下进行开发,这个时候我们通常不会去操作SqlSession,而且在mybatis-spring的插件包中也会在每次执行完后就commit(详细的等看完了再来补充)
mybatis的懒加载机制
当我们在查询一条订单记录的时候,自然会同时查出该订单记录关联的用户表中的那条记录,但是反过来,当我们在查一个用户记录的时候,通常不会对该用户名下所有的订单记录都进行查询,这无疑会大大增加服务器的开销,并且也不符常理,懒加载机制就是为了解决这个问题。
同样的,这里还是从用户表与账户表的模型出发,需求是查询账单表的每条记录并且查询出所对应的用户信息。
<settings>
<!--设置延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--加载所有属性改为按需加载相应的字段-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
开启延迟加载,并且不会加载所有的字段,只查询所使用到的字段
<select id="findAll" resultMap="accountUser">
select * from account
</select>
这里就不能再将user表和account表进行连接查询查两张表了,因为这里要使用懒加载机制,如果还是和先前那样通过inner join查询那么一定会查询两张表,也就不存在懒加载这么一说了。
<resultMap id="accountUser" type="account">
<!--映射account的数据库字段和实体类的映射-->
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--一对一的关系映射:配置封装user的内容-->
<!--select属性指定的内容:因为使用的是懒加载,所以只有在使用到用户信息的时候才会去加载用户信息,这个select标签的属性就是用了定义,需要用到用户信息的时候通过哪个方法去查询用户-->
<!--column属性指定的内容:根据id去查询用户信息时,我们需要用到哪个参数的值作为条件去查询-->
<association fetchType="lazy" property="user" javaType="user" column="uid" select="mybatis.dao.UserDao.findUserById">
</association>
</resultMap>
//通过使用account中的user信息和不使用user信息来观察查询结果
@Test
public void findAccount() {
List<Account> accounts = accountDao.findAll();
//accounts.forEach(a -> System.out.println(a + " \n " + a.getUser()));
}
我们可以清除的看到此时并没有发送执行查询user表的sql语句,现在把注释打开,通过调用getUser方法再来观察查询结果
这个时候就可以明显发现执行了查询user表的sql语句,同时也打印出了user的相关信息,以上就是在一对一的情况下的懒加载。下面来简单说一下一对多情况下的懒加载,其实和一对一没有什么区别,除了在配置resultMap时的标签不同,这里从user的角度出发,查询一个用户的同时如果在用到用户下的账户信息的时候就去查询account表,否则就不查询。
user的相关配置:
List<User> findUserByLazy();
<resultMap id="lazyUser" type="user">
<id property="userId" column="id"/>
<result property="userName" column="username"/>
<result property="userBirthday" column="birthday"/>
<result property="userSex" column="sex"/>
<result property="userAddress" column="address"/>
<!--注意:collection中的column属性填的是当要查询account的时候要通过哪个字段的属性值去查询,不是java pojo中的属性名-->
<collection fetchType="lazy" property="accounts" ofType="account" column="id" select="mybatis.dao.AccountDao.findAccountByUid"/>
</resultMap>
<select id="findUserByLazy" resultMap="lazyUser">
select * from user
</select>
同样的,在AccountDao与其对应的映射文件中需要提供findAccountByUid方法的签名以及查询的sql语句
List<Account> findAccountByUid();
<select id="findAccountByUid" resultType="account">
select * from account where uid = #{id}
</select>
mybatis的注解开发
当使用mybatis的注解开发的时候就不再需要映射文件了,但是要注意的是如果项目中在同目录下任然存在映射文件,那么不管是否引入了映射文件,mybatis都会报错,所以这里我们就换了一个目录建包来测试。
<mappers>
<!--这里需要引入的包的路径就是相应的mapper接口所在的路径-->
<package name="mybatis.mapper"/>
</mappers>
数据库的字段名与之前一样不做改动,此时我们将User实体类中的成员变量名改的和数据库不一致
public class User implements Serializable {
//注解方式如何处理实体类与数据库字段名的不一致问题,在UserMapper中配置Results注解
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddress;
}
注解开发的查询操作
package mybatis.mapper;
import mybatis.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
//Results中的id属性可以给其他方法通过@ResultMap注解使用
@Select("select * from user")
//因为这个时候数据库的字段名与java pojo的成员变量名不再对应,所以通过Results注解(其实就相当于xml中的resultMap标签)
@Results(id= "userMap",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")
})
List<User> findAll();
}
原本的xml配置方式开发,从映射文件到接口再到实体类,我们可以看到对应关系都由注解的方式指明了。
<select id = "findById" parameterType = "int" resultType="user">
select * from user by id = #{id}
</select>
这是普通xml的方式,可以看到实体类通过别名的方式对这个entity包进行了配置,所以只要在返回类型使用别名user就可以指定,参数值通过parameterType进行指定,再下来就是对应方法名,在映射文件的mapper标签中有一个namespace属性就配置了当前映射文件所对应的dao比如UserDao.xml的mapper标签的namespace属性为"",这样就建立了映射文件与UserDao接口的关联,最后只要根据select标签中id属性值对应方法名即可,那么在注解开发中很容易就实现这些联系。
注解开发的更新操作
@Update("update user set username=#{userName},address=#{userAddress},sex=#{userSex},birthday=#{userBirthday} where id=#{userId}")
void update(User user);
//这里需要注意的是mybatis中的实体类与数据解析使用的是OGNL表达式,简而言之就是通过pojo.属性名来获取数据
//底层应该是通过get方法来获取,所以sql语句中的username=#{userName} #{}里面的值必须和pojo类的字段名保持一致
注解开发的保存操作
@Insert("insert into user(username,address,sex,birthday) values (#{userName},#{userAddress},#{userSex},#{userBirthday})")
//@Options(useGeneratedKeys=true, keyProperty="userId", keyColumn="id")
@SelectKey(statement = "select last_insert_id()",keyColumn = "id",keyProperty = "userId",before = false,resultType = Integer.class)
Integer saveUser(User user);
//使用option注解和SelectKey注解中的其中一个都可以返回主键id的自增值
注解开发的删除操作
@Delete("delete from user where id = #{uid}")
Integer deleteUserById(int id);
//因为此时的参数值只有一个id,所以#{}中的值只是个符号,可以随便填,mybatis都能正确为它赋值,这点与xml中也是一致的
注解开发的多表查询-一对一
这里仍然采用user与account的模型,从account出发,某一条account记录与user表是一对一的关系。
@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"),
//这里的colum值必须要填数据库中的字段名,不能填pojo的属性名
@Result(property = "user",column = "uid",one =
@One(select = "mybatis.mapper.UserMapper.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();
//userMapper
@Select("select * from user where id = #{id}")
@ResultMap("userMap")
User findById(Integer id);
注解开发的多表查询-一对多
@Select("select * from user")
@Results(id= "userMap",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"),
@Result(property = "accounts",column = "id",many =
@Many(fetchType = FetchType.LAZY,select = "mybatis.mapper.AccountMapper.findAccountsByUid")
)
})
List<User> findAll();
@Select("select * from account where uid = #{id}")
@ResultMap("accountMap")
List<Account> findAccountsByUid(Integer id);