延迟加载
一对一:
在MyBatis中,一对一的实现是靠<association>
标签实现的,设置第二步查询的入口属性column
赋予参数,传递给select此属性指向的从表实体类的操作方法,即可实现。还要注意延迟加载<configuration>
下<environments>
标签前设置延迟加载开关:
<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>
实质上延迟加载需要到用到主表从表两个Dao配置文件,主表配置文件中只需要写查询自己的SQL语句,并把参数传过去调用从表属性的配置文件实现封装。
当测试方法只是单纯调用延迟加载方法时,并未对查询得到的结果操作,这时只是会执行主表配置文件的查询语句,而后续对从表实体类的查询并未进行。当然主表实体类一经操作,自然调用所有查询,即从表实体类也会立即查询得到结果并封装。
第一步查询进行时
缓存机制
一级缓存
在
myBatis
中,
dao
操作类接口是由
sqlSession
代理实现的,所以多个不同的
dao
可以由同一个
sqlSession
实现,此时不同的
dao
拥有同样的一级缓存,进行同样的查询操作不会重新执行,即一级缓存中拥有同一的缓存对象。当
sqlSession
关闭或者清除缓存之后,两个
dao
将会执行两次同样的查询。
sqlSession.clearCache();//清除sqlSession.close();//关闭
值得注意,一级缓存是 SqlSession
范围的缓存,当调用 SqlSession
的修改
,添加
,删除
,commit()
,close()
等方法时,就会清空一级缓存。
二级缓存
在myBatis
中,二级缓存是 mapper
映射级别的缓存,多个 SqlSession
去操作同一个 Mapper
映射的 sql
语句,多个 SqlSession
可以共用二级缓存,二级缓存是跨 SqlSession
的。 由于SqlSessionFactory
中开启的SqlSession
,二级缓存是在SqlSessionFactory
对象的缓存区上。当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable
接口,这种就可以使用序列化 方式来保存对象。
在一级缓存关闭的情况下,即使二级缓存正确开启使用,所取出的缓存对象也是不一致的,主要是二级缓存是通过存储序列化信息方式保存的对象内容,反序列化就重新实例化了新对象。但是SQL语句确实只是执行了一遍。
开启步骤:
第一步:在 主配置文件SqlMapConfig.xml
开启二级缓存
<settings> <!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/> </settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为 false 代表不开启二级缓存。
第二步:配置相关的 Mapper
映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
第三步:配置 statement
上面的 useCache
属性
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml
映射文件中的<select>
标签中设置 useCache=”true”
代表当前这个 statement
要使用 二级缓存,如果不使用二级缓存可以设置为 false
。 注意:针对每次查询都需要最新的数据 sql
,要设置成 useCache=false
,禁用二级缓存。
注解开发
基本CRUD
//注解方式,只需要传递String[]类型单个参数value,可以省略value=//注解方式,只需要传递String[]类型单个数组元素,大括号也可省略//查询所有操作@Select("select * from user")List<User> findAll();//添加User操作@Insert("insert into user(username,birthday,sex,address) " +"values(#{username},#{birthday},#{sex},#{address})")int saveUser(User user);//更新User@Update("update user set username=#{username}," +"birthday=#{birthday},sex=#{sex},address=#{address} " +"where id=#{id}" )void updateUser(User user);//删除User//因为传入参数没有涉及到User类,只是简单参数类型,#{}里面可以乱填@Delete("delete from user where id=#{i} ")void deleteUser(Integer userId);//查询具体ID用户@Select("select * from user where id=#{i} ")User findById(Integer userId);//模糊查询//传入参数String也是基本数据类型对象,随意指定#{}里面内容//@Select("select * from user where username like #{name}")@Select("select * from user where username like '%${value}%' ")List<User> findUserByNameFuzzy(String username);//聚合函数的应用@Select("select count(*) from user ")int findTotal();
实体类与数据库表属性映射问题
@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") })
//下面方式复用映射@Select("select * from user where id = #{uid} ") @ResultMap("userMap")
一对一
主表Dao操作类中的方法注释配置:
一对多
主表Dao操作类中的方法注释配置:
一对一、一对多差别在于内置对象是单个或者多个组成的集合,和基于XML的配置方式思想如出一辙,具体根据情况选择合适的参数。
开启二级缓存
可以发现,SqlSession
代理实现同一个接口时,放在缓存区的同一对象不会再次查询,这是一级缓存,是基于SqlSession
对象的缓存区。无论基于XML还是注解开发都是默认开启的。
关于二级缓存
,接口注解要设置(没有默认开启):
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {}
同时主配置文件SqlMapConfig.xml
中也要开启(虽然是默认开启的):
<!-- 配置二级缓存 -->
<settings> <!-- 开启二级缓存的支持 --> <setting name="cacheEnabled" value="true"/> </settings>
关于开启二级缓存的实验:
@Testpublic void testLevel2Cache(){/*** 不同的Session,不同的缓存区,前一个没有关闭的话,二级缓存没写入,后一个找不到同语句对应对象,会再次发起查询* 不同的Session,不同的缓存区,关闭前一个的话,后一个会在二级缓存读取数据,只会查询一次*/SqlSession sqlSession0=sqlSessionFactory.openSession();IUserDao iUserDao1=sqlSession0.getMapper(IUserDao.class);User user=iUserDao1.findById(46);System.out.println(user);sqlSession0.close();SqlSession sqlSession1=sqlSessionFactory.openSession();System.out.println("SqlSession是否同一个");System.out.println(sqlSession0==sqlSession1);IUserDao iUserDao2=sqlSession1.getMapper(IUserDao.class);User user1=iUserDao2.findById(46);System.out.println(user1);System.out.println(user==user1);sqlSession1.close();/*** 同一个Session,一级缓存清空,会查询两次,不清空只会查询一次*//* IUserDao iUserDao1=sqlSession.getMapper(IUserDao.class);User user=iUserDao1.findById(46);System.out.println(user);sqlSession.clearCache();IUserDao iUserDao2=sqlSession.getMapper(IUserDao.class);User user1=iUserDao2.findById(46);System.out.println(user1);System.out.println(user==user1);*/}
对于只执行了一遍查询语句,动用不同SqlSession
也能输出两次同样的数据必定存在非SqlSession
的缓存区(一级缓存),正是二级缓存起作用。至于为什么对象信息一样,却不是同一个,已经做过记录。
有个现象是,在前一个SqlSession
未关闭时,后一个SqlSession
发起查询操作,也会调用查询语句,并非直接在二级缓存中取数据。不排除二级缓存的保存操作是在上一个SqlSession
关闭后,二级缓存才写入数据。后一个一开始在二级缓存区检索并无对应对象,才会直接再次发起查询。
IDEA:找不到主类错误
动手改着项目结构增加几个小模块时,突然出现了这么一个错误,借鉴前辈的解决方法:
IDEA错误: 找不到或无法加载主类解决方法
这个时候啥子编译错误的信息提示都没有,就很慌,丈二和尚摸不着头脑。基本和这位大佬一样的情况,原因大概是:乱改项目结构会导致整个项目标记资源文件夹和子模块的混乱交叉,从而编译的时候出的错找不出原因。同样最后打开项目结构把大项目的文件夹的资源标记全部清除干净,完事。很底层的错误,虽然解决不费力气但还是希望不要遇到狗咬刺猬的下场。