内容
- Mybatis中的延迟加载,什么是延迟加载,什么是立即加载
- Mybatis中的缓存,为什么使用缓存,什么样的数据能使用缓存,什么样的数据不能使用
- Mybatis中的一级缓存和二级缓存
- Mybatis中的注解开发, 环境搭建,单表CRUD操作(代理Dao方式),多表查询操作,缓存的配置
Mybatis中的延迟加载
问题:在一对多中,当我们有一个用户,它有100个账户 在查询用户的时候,要不要把关联的账户查出来?
内存中开辟的空间
有一个用户对象,同时里面有一个accounts集合,引用了一个存放100个accounts对象的集合,造成了很大的内存开销
内存开销图
在查询用户时,用户下的账户信息应该时什么时候使用,什么时候查询
问题:在查询账户的时候要不要把关联的用户查出来?
在查询账户时,账户的所属用户信息应该时随着账户查询时一起查询出来的
延迟加载
在真正使用数据时,才发起查询,不用的时候不查询。按需加载(懒加载)
立即加载
不管用不用,只要一调用方法,马上发起查询
Mybatis中的表关系
在对应的四种表关系中:一对多,多对一,一对一,多对多
Mybatis中分成两组
一对多,多对多 如果关联的对象是多,通常情况下我们采用延迟加载(用户查询的他的账户)
多对一,一对一 如果关联的对象是一,我们都是采用立即加载(查询一个账户就要看到所属的用户信息,查询一个人的时候就想知道身份证号)
Mybatis一对一实现延迟加载
查询账户延迟加载用户
IAccountDao.xml
<mapper namespace="com.tju.dao.IAccountDao">
<!-- 定义封装account和user的resultMap -->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 一对一的关系映射:配置封装user的内容
select属性指定的内容:查询用户的唯一标识,到IUserDao.xml中找查询用户的配置接口方法:
column属性指定的内容:用户根据id查询时,所需要的参数的值
-->
<association property="user" column="uid" javaType="user" select="com.tju.dao.IUserDao.findById"></association>
</resultMap>
<!-- 根据用户id查询账户列表 -->
<select id="findAccountByUid" resultType="account">
select * from account where uid = #{uid}
</select>
</mapper>
IUserDao.xml
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user">
select * from user where id = #{uid}
</select>
IAccountDao.java
public interface IAccountDao {
/**
* 查询所有账户,同时还要获取到当前账户的所属用户信息
* @return
*/
List<Account> findAll();
}
延迟加载配置SqlMapConfig.xml
<!--配置参数-->
<settings>
<!--开启Mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
延迟加载测试
@Test
public void testFindAll(){
List<Account> accounts = accountDao.findAll();
for(Account account : accounts){
System.out.println("--------每个account的信息------------");
System.out.println(account);
System.out.println(account.getUser());
}
Mybatis中实现一对多的延迟加载
查询用户延迟查询用户底下的所有账户
IUserDao.xml
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 定义User的resultMap-->
<resultMap id="userAccountMap" 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>
<!-- 配置user对象中accounts集合的映射,延迟加载用到再去.IAccountDao.findAccountByUid配置文件查询账户信息 -->
<collection property="accounts" ofType="account" select="com.tju.dao.IAccountDao.findAccountByUid" column="id"></collection>
</resultMap>
<!-- 查询所有 -->
<select id="findAll" resultMap="userAccountMap">
select * from user
</select>
</mapper>
IAccountDao.java
.public interface IAccountDao {
/**
* 根据用户id查询账户信息
* @param uid
* @return
*/
List<Account> findAccountByUid(Integer uid);
}
IAccountDao.xml
<!-- 根据用户id查询账户列表,延迟加载找到这里 -->
<select id="findAccountByUid" resultType="account">
select * from account where uid = #{uid}
</select>
测试延迟加载
/**
* 测试查询所有
*/
@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());
// }
}
Mybatis中的缓存
什么是缓存?
存在于内存中的临时数据
为什么使用缓存?
减少和数据库的交互次数,提高执行效率。
什么样的数据能使用缓存,什么样的数据不能使用/
使用于缓存:经常查询,并且不经常改变的。
数据的正确与否对最终结果影响不大的
不适用于缓存
经常改变的数据
数据的正确与否对最终结果影响很大的
Mybatis中的一级缓存
一级缓存:
它指的是Mybatis中的SqlSession对象的缓存
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中
该区域结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用
当SqlSession对象消失时,mybatis中的一级缓存也就消失了
测试一级缓存
两种方法清空缓存,不使用的话,mybatis直接从一级缓存中查询数据
方法一:关闭SqlSession再开启
方法二:调用sqlsession对象的情况缓存方法
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache(){
User user1 = userDao.findById(41);
System.out.println(user1);
//方法一 关闭sqlsession
// sqlSession.close();
//再次获取SqlSession对象
// sqlSession = factory.openSession();
//方法二
sqlSession.clearCache();//此方法也可以清空缓存
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);
}
触发情空一级缓存的情况
当调用SqlSession的修改,添加,删除,commit(),close()等方法时就会清空一级缓存
示例:更新时会清空一级缓存中的信息,更新前和更新后查询得到的数据并不一样
IUserDao.java
public interface IUserDao {
/**
* 更新用户信息
* @param user
*/
void updateUser(User user);
}
IUserDao.xml
<!-- 更新用户信息-->
<update id="updateUser" parameterType="user">
update user set username=#{username},address=#{address} where id=#{id}
</update>
两次的user并不一样,因为调用update缓存被清空了
/**
* 测试缓存的同步
*/
@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);
}
mybatis中的二级缓存
它指的时Mybatis中的SqlSessionFactory对象的缓存
由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
二级缓存原理图
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
在SqlMapconfig中配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
全局地开启或关闭配置文件中所有映射已经配置的任何缓存(默认是true,就是开启)
在IUserDao开启Mybatis中的二级缓存
<!--开启user支持二级缓存-->
<cache/>
在id查询的select标签中配置二级缓存:useCache=true
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
</select>
测试二级缓存
@Test
public void testSecondLevelCache(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();//一级缓存消失
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2); //false
}
虽然使用了二级缓存,而第二次查询没有操作数据库而是直接到二级缓存中取找数据
但仍然是两个user对象
因为二级缓存存放的是一散装数据,使用的时候还得创建的对象,所以只是使用相同的数据并不是相同的对象
Mybatis中的注解开发
取代的是原来配置文件IUserDao.xml
SqlMapConfig.xml
<configuration>
<!-- 引入外部配置文件-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置实体类的别名-->
<typeAliases>
<package name="com.tju.domain"></package>
</typeAliases>
<!-- 配置环境-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 指定带有注解的dao接口所在位置 -->
<mappers>
<mapper class="com.tju.dao.IUserDao"></mapper>
</mappers>
</configuration>
Mybatis注解开发测试和使用的注意事项
在接口上加注解
在mybatis中针对,CRUD一共有四个注解
- @Select @Insert @Update @Delete
IUserDao.java
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();
}
测试
public class MybatisAnnoTest {
/**
* 测试基于注解的mybatis使用
* @param args
*/
public static void main(String[] args) throws Exception{
//1.获取字节输入流
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.根据字节输入流构建SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.根据SqlSessionFactory生产一个SqlSession
SqlSession session = factory.openSession();
//4.使用SqlSession获取Dao的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5.执行Dao的方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
}
注解开发分析
原来用IUserDao.xml中的内容直接用注解就表示了
如果SqlMapConfig中使用
<mappers>
<package name="com.tju.dao"></package>
</mappers>
在注解和接口.xml同时存在的情况下用不了
如果使用注解配置使用,在同时情况下仍然报错
<mappers>
<mapper class="com.tju.dao.IUserDao"></package>
</mappers>
所以,注意两者不能同时存在
mybatis解开发和保存更新功能以及CURD
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();
/**
* 保存用户
* @param user
*/
@Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")
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=#{id} ")
User findById(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();
}
测试类
public class AnnotationCRUDTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Before
public void init()throws Exception{
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy()throws Exception{
session.commit();
session.close();
in.close();
}
@Test
public void testSave(){
User user = new User();
user.setUsername("mybatis annotation");
user.setAddress("北京市昌平区");
userDao.saveUser(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(57);
user.setUsername("mybatis annotation update");
user.setAddress("北京市海淀区");
user.setSex("男");
user.setBirthday(new Date());
userDao.updateUser(user);
}
@Test
public void testDelete(){
userDao.deleteUser(51);
}
@Test
public void testFindOne(){
User user = userDao.findById(57);
System.out.println(user);
}
@Test
public void testFindByName(){
// List<User> users = userDao.findUserByName("%mybatis%");
List<User> users = userDao.findUserByName("mybatis");
for(User user : users){
System.out.println(user);
}
}
@Test
public void testFindTotal(){
int total = userDao.findTotalUser();
System.out.println(total);
}
}
mybatis注解建立实体类属性和数据库表中列的对应关系
当实体类属性和数据表不对应时候使用@Results
使用@Results注解
@Results注解图
String id() 是方便其他注解引用
Result[] value() 是Result注解的数组
@Result注解
boolean id()指明所在列是否为主键
String column() 指明数据库的列
String proprety 指明实体类的属性
示例
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",value={
@Result(id=true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
@Result(property = "accounts",column = "id",
many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
如果接口的其他方法也要使用以上注解配置的表和实体类的信息
那么使用@ResultMap注解,引用上述的id=userMap的Results注解
@Select("select * from user where id=#{id} ")
@ResultMap("userMap")
User findById(Integer userId);
mybatis注解开发一对一的查询配置
查询所有账户,并获取每个账户所属的用户信息
新建Account实体类,在Account实体类中封装user对象
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//多对一(mybatis中称之为一对一)的映射:一个账户只能属于一个用户
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}
}
IAccountDao.java
使用注解配置findAll接口 用@Results注解里的@Result注解封装user信息
图 @Result注解在一对一的使用
@Result注解中的one注解
String select() 知道账户的用户id,查询用户的接口方法,也就是全限定类名+方法名
所需要信息
FetchTypy fetchType()
配置延迟和立即加载
所以配置user的Result注解
@Result(property = "user",column = "uid",one=@One(select="com.tju.dao.IUserDao.findById",fetchType= FetchType.EAGER))
public interface IAccountDao {
/**
* 查询所有账户,并且获取每个账户所属的用户信息
* @return
*/
@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.tju.dao.IUserDao.findById",fetchType= FetchType.EAGER))
})
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());
}
Mybatis中一对多的配置
查询一个用户显示多个账户的信息
在User实体类中封装Account信息
public class User implements Serializable{
private Integer userId;
private String userName;
private String userAddress;
private String userSex;
private Date userBirthday;
//一对多关系映射:一个用户对应多个账户
private List<Account> accounts;
public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserSex() {
return userSex;
}
public void setUserSex(String userSex) {
this.userSex = userSex;
}
public Date getUserBirthday() {
return userBirthday;
}
public void setUserBirthday(Date userBirthday) {
this.userBirthday = userBirthday;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAddress='" + userAddress + '\'' +
", userSex='" + userSex + '\'' +
", userBirthday=" + userBirthday +
'}';
}
}
在查询用户的findAll方法上加上注解配置account的Result注解
使用Result注解中的many属性,用many注解配置
many注解
select属性指定根据账户的用户id查用户的接口方法的全限定类名+方法名
fetchType设置为延迟加载
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",value={
@Result(id=true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
@Result(property = "accounts",column = "id",
many = @Many(select = "com.tju.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
List<User> findAll();
}
注解开发使用二级缓存
mybatis全局开始二级缓存
<!--配置开启二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在接口上配置开启二级缓存
@CacheNamespace(blocking = true)
public interface IUserDao {