MyBatis学习第四天
1. MyBatis中的延迟加载
1.1 立即加载和延迟加载
在我们前面的学习中,我们举了用户和账户的例子,如果有一个用户,它有1000个账户甚至更多,那么在我们查询的时候问题就来了,
- 在查询用户的时候,要不要把关联的账户查出来?
- 在查询账户的时候,要不要把关联的用户查出来?
对于我们来说,当然是需要的时候再去查询,不需要就不查询。例如问题1,当查询用户的时候,我只关心用户的基本信息,不关心用户的账户信息,那么这时就不应该把用户的账户信息也查出来。如果每次查询用户的时候都同时把用户的账户信息也查出来,这样会占用很多额外的开销,账户信息在需要的时候去查询即可。但是对于问题2就不一样了,因为每个账户只有一个用户,在查询账户的时候当然希望吧账户所属的用户信息也查询出来。
立即加载
不管用不用,只要一调用方法,马上发起查询。
延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
- 好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
- 坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
在对应的四种表关系(一对多,多对一,一对一,多对多)中建议使用的加载方式
表关系 | 加载方式 |
---|---|
一对多,多对多 | 通常情况下我们都是采用延迟加载(懒加载) |
多对一,一对一 | 通常情况下我们都是采用立即加载 |
1.2 延迟加载的使用
首先在当前的工程下新建一个Module命名为 day04_01lazy,然后把之前创建的Module day03_03mybatis_one2many里src下的文件全部拷贝到 day04_01lazy 的src里,不要忘记修改pom.xml文件,然后把无关的文件都删除掉只保留用于测试延迟加载的文件,Module的结构如下:
- 开启MyBatis的延迟加载策略,进入 Mybaits 的官方文档,找到 settings 的说明信息:
- 修改MyBatis的主配置文件,在settings中添加 lazyLoadingEnabled 和 aggressiveLazyLoading 的配置,主配置文件SqlMapperConfig.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--上面是mybatis的主配置文件的约束-->
<!--"configuration的配置必须按照这个顺序否则报错:
(properties,settings,typeAliases,typeHandlers,objectFactory,objectWrapperFactory,
reflectorFactory,plugins,environments,databaseIdProvider,mappers)"-->
<configuration>
<!-- 配置properties-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置参数-->
<settings>
<!--开启MyBatis支持延迟加载的全局开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载-->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
<!--使用typeAliases配置别名,它只能配置domain中类的别名-->
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类的全限定类名。alias属性指定别名,当指定了别名就不在区分大小写-->
<!--<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>-->
<!--用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不在区分大小写-->
<package name="com.itheima.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--配置映射文件的位置-->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>
<mapper resource="com/itheima/dao/IAccountDao.xml"></mapper>
</mappers>
</configuration>
注意:configuration 配置标签的顺序
properties,settings,typeAliases,typeHandlers,objectFactory,objectWrapperFactory,reflectorFactory,plugins,environments,databaseIdProvider,mappers。必须按照这个顺序否则报错,下图展示的官方文档中所要求的的顺序。
1.3.1 一对一的延迟加载
一对一的延时加载,我们通过查询账户信息,同时查询出账户所属的用户信息来学习。
- IAccountDao.java 接口里的方法只保留 findAll 和 findAccountByUid 方法。
public interface IAccountDao {
/**
* 查询所有的账户,同时还要获取当前账户的所属用户信息
* @return
*/
List<Account> findAll();
/**
* 根据用户的id查询账户
* @param uid 用户id
* @return
*/
List<Account> findAccountByUid(Integer uid);
}
- IUserDao.java 接口里的方法只保留 findAll 和 findById 方法。
public interface IUserDao {
/**
* 查询所有用户的操作,同时获取到用户下所有账户的信息
* @return
*/
List<User> findAll();
/**
* 根据id查询用户
* @param userId 用户id
* @return
*/
User findById(Integer userId);
}
- IAccountDao 的修改映射文件IAccountDao.xml,注意 association 标签里的 select 属性表示延迟加载时要调用的方法,这里的colum的值就是其参数
<?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表示Dao的接口-->
<mapper namespace="com.itheima.dao.IAccountDao">
<!-- 定义封装account和user的resultMap -->
<resultMap id="accountUser" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- 一对一的关系映射:配置封装user的内容
select属性指定的内容:查询用户的唯一标识
column属性指定的内容:用户根据id查询时,所需要的参数的值
-->
<association property="user" column="uid" javaType="user"
select="com.itheima.dao.IUserDao.findById">
</association>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="accountUser">
select * from account;
</select>
<!--根据用户id查询账户列表-->
<select id="findAccountByUid" parameterType="integer" resultType="account">
select * from account where uid =#{uid};
</select>
</mapper>
- 下面修改测试类 AccountTest.java
public class AccountTest {
private InputStream in = null;
private SqlSession session = null;
private IAccountDao dao = null;
/**
* 初始化Mybatis
* @throws Exception
*/
@Before //用于在测试方法之前执行
public void init() throws Exception {
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.获取SqlSessionFactor对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sessionFactory = builder.build(in);
//3.获取SqlSession对象
session = sessionFactory.openSession();
// session = sessionFactory.openSession(true); //这样就不用事务的手动提交了,就会自动提交
//4.获取Dao的代理对象
dao = session.getMapper(IAccountDao.class);
}
/**
* 释放资源
* @throws Exception
*/
@After //用于在测试方法之后执行
public void destroy() throws Exception {
//提交事务
session.commit();
if (in != null)
in.close();
if (session != null)
session.close();
}
/**
* 查询所有的测试
*/
@Test
public void testFindAll() {
try {
//5.查询所有方法
List<Account> accounts = dao.findAll();
for (Account account : accounts) {
System.out.println(account);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 我们运行测试方法 testFindAll 之后控制台输出如下:
共执行了3次SQl,程序同时把账户和用户信息都查出来了,这是为什么呢?,这是因为我们 执行 System.out.println(account); 这一句代码,这会去执行 account.toString 方法,代码如下,然后在去执行 user.toString 方法,因为我们用到了user的数据所以触发去查询账户的所属用户信息。
@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
", user=" + user +
'}';
}
- 通过上面的分析,可知当我们在测试方法中吧输入注释掉,这是在执行测试方法。
@Test
public void testFindAll() {
try {
//5.查询所有方法
List<Account> accounts = dao.findAll();
for (Account account : accounts) {
// System.out.println(account);
}
} catch (Exception e) {
e.printStackTrace();
}
}
控制台输出如下,从图中可以看出只执行了一句SQL,说明数据延时加载了。
1.3.2 一对多的延迟加载
一对多的延迟加载,我们通过查询用户信息同时查询用户的账户信息来学习。
- 上面我们已经修改过了MyBatis的主配置文件,就不用修改了,下面修改 IUserDao 的 映射配置文件 IUserDao.xml,把 collection 标签里的内容删除掉,然后添加属性 select 其值为 延迟加载时要调用的方法,column 的值就是其参数。
<?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表示Dao的接口-->
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 定义user的resultMap-->
<resultMap id="userAccountMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<!-- 配置user对象中accounts的集合映射 -->
<collection property="accounts" ofType="account"
select="com.itheima.dao.IAccountDao.findAccountByUid"
column="id"></collection> <!--这个id是用户的id,因为是用用户的id去查询-->
</resultMap>
<select id="findAll" resultMap="userAccountMap" resultType="user">
select * from user;
</select>
<!--根据id查询一个用户-->
<select id="findById" parameterType="Integer" resultType="User">
select * from user where id=#{uid};
</select>
</mapper>
- 修改测试类 UserTest.java
public class UserTest {
private InputStream in = null;
private SqlSession session = null;
private IUserDao dao = null;
/**
* 初始化Mybatis
* @throws Exception
*/
@Before //用于在测试方法之前执行
public void init() throws Exception {
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.获取SqlSessionFactor对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sessionFactory = builder.build(in);
//3.获取SqlSession对象
session = sessionFactory.openSession();
//4.获取Dao的代理对象
dao = session.getMapper(IUserDao.class);
}
/**
* 释放资源
* @throws Exception
*/
@After //用于在测试方法之后执行
public void destroy() throws Exception {
//提交事务
session.commit();
if (in != null)
in.close();
if (session != null)
session.close();
}
/**
* 查询所有的测试
*/
@Test
public void testFindAll() {
try {
//5.查询所有方法
List<User> users = dao.findAll();
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 运行测试方法 testFindAll 你会发现程序把用户查询出来了而且还把账户的信息查询出来了,为什么了这个原因和上面测试一对一延时加载时的原因一样。其实已经实现了延时加载。
2. MyBatis中的缓存
- 什么是缓存
- 存到内存中的临时数据。
- 为什么要使用缓存
- 为了减少和数据库的交互次数,减少io操作,提高执行效率
-2.3 什么样的数据能使用缓存,什么样的数据不能使用缓存 - 在使用缓存时,自然会遇到数据不同步的问题,这就涉及到什么样的数据适合使用缓存,什么样的数据不适合使用缓存。
- 为了减少和数据库的交互次数,减少io操作,提高执行效率
适用于使用缓存的数据 | 不适用缓存的数据 |
---|---|
经常查询并且不经常改变的 | 经常改变的数据 |
数据的正确与否对最终的结果影响不大的 | 数据的正确与否对最终的结果影响很大的数据 |
例如:商品的库存 | 例如:银行的汇率,股市的股价 |
像大多数的持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数, 从而提高性能。
Mybatis 中缓存分为一级缓存,二级缓存。
2.4 MyBatis中的一级缓存和二级缓存
首先在当前的工程下新建一个Module命名为 day04_02cache,然后把之前创建的Module day03_03mybatis_one2many里src下的文件全部拷贝到 day04_02cache的src里,不要忘记修改pom.xml文件然后把无关的文件都删除掉只保留用于测试缓存的文件,Module的结构如下:
2.4.1 一级缓存
MyBatis的一级缓存,它指的是MyBatis中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。该区域的结构是一个Map。当我再次查询同样的数据,mybatis会先去SqlSession中查询是否有,有的话就直接读取。当SqlSession对象消失时,mybatis的一级缓存也就消失了。一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
一级缓存是SqlSession范围的缓存,当调用SqlSession的修改、添加、删除、commit()、close()等方法时,就会清空一级缓存。
- 修改MyBatis的主配置文件 SqlMapperConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--上面是mybatis的主配置文件的约束-->
<configuration>
<properties resource="jdbcConfig.properties"></properties>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。-->
<!--<settings>
<setting name="cacheEnabled" value="true"/>
</settings>-->
<!--使用typeAliases配置别名,它只能配置domain中类的别名-->
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类的全限定类名。alias属性指定别名,当指定了别名就不在区分大小写-->
<!--<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>-->
<!--用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不在区分大小写-->
<package name="com.itheima.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--配置映射文件的位置-->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
- 修改IUserDao.java把无关的方法删掉,只保留 findById 和 updateUser 两个方法
public interface IUserDao {
/**
* 根据id查询用户
* @param userId 用户id
* @return
*/
User findById(Integer userId);
/**
* 更新用户信息
* @param user 用户
*/
void updateUser(User user);
}
- 修改IUserDao的映射配置文件 IUserDao.xml
<?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表示Dao的接口-->
<mapper namespace="com.itheima.dao.IUserDao">
<!--开启user支持二级缓存-->
<!--<cache/>-->
<!--根据id查询一个用户-->
<select id="findById" parameterType="Integer" resultType="User">
select * from user where id=#{uid};
</select>
<!--更新用户信息-->
<update id="updateUser" parameterType="user">
update user set username = #{username},sex= #{sex},address= #{address},birthday= #{birthday} where id = #{id};
</update>
</mapper>
- 编写测试类 User.java
public class UserTest {
private InputStream in = null;
SqlSessionFactory sessionFactory = null;
private SqlSession session = null;
private IUserDao dao = null;
/**
* 初始化Mybatis
* @throws Exception
*/
@Before //用于在测试方法之前执行
public void init() throws Exception {
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.获取SqlSessionFactor对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
sessionFactory = builder.build(in);
//3.获取SqlSession对象
session = sessionFactory.openSession();
//4.获取Dao的代理对象
dao = session.getMapper(IUserDao.class);
}
/**
* 释放资源
* @throws Exception
*/
@After //用于在测试方法之后执行
public void destroy() throws Exception {
//提交事务
session.commit();
if (in != null)
in.close();
if (session != null)
session.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache() {
User user1 = dao.findById(4);
System.out.println(user1);
//session.close();
//再次获取SqlSession对象
//session = sessionFactory.openSession();
//重新获取dao
//dao = session.getMapper(IUserDao.class);
//session.clearCache();//此方法也可以清空缓存
User user2 = dao.findById(4);
System.out.println(user2);
System.out.println(user1 == user2);
}
/**
* 测试缓存同步
*/
@Test
public void testClearCache() {
//1.先去查询用户
User user1 = dao.findById(3);
System.out.println(user1);
//2.跟新用户信息
user1.setUsername("update user clear cache");
user1.setAddress("北京海淀区");
dao.updateUser(user1);
//3.再一次查询id为3的用户
User user2 = dao.findById(3);
System.out.println(user2);
System.out.println(user1 == user2);
}
}
- 运行测试方法 testFirstLevelCache 控制台输出如下,值查询了一次数据库,并且两个对象的地址相同,其证明了一级缓存的存在。
- 测试方法 testFirstLevelCache 做如下修改
public void testFirstLevelCache() {
User user1 = dao.findById(4);
System.out.println(user1);
session.close();
//再次获取SqlSession对象
session = sessionFactory.openSession();
//重新获取dao
dao = session.getMapper(IUserDao.class);
//session.clearCache();//此方法也可以清空缓存
User user2 = dao.findById(4);
System.out.println(user2);
System.out.println(user1 == user2);
}
然后在一次运行 测试方法 testFirstLevelCache 控制台输出如下,执行了两次数据库的查询语句,并行两个对象的地址不相同。证明了当SqlSession关闭之后,一级缓存就会消失。
- 测试缓存的同步
运行测试方法 testClearCache 控制台输出如下,可以发现当执行update操作后,在一次查询用户时是从数据库查询的。
2.4.2 二级缓存
它指的是MyBatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
2.4.2.1 二级缓存的使用步骤
-
要想使用MyBatis的二级缓存就必须进行一下配置
第一步:让MyBatis框架支持二级缓存(在SqlMapConfig.xml中配置)
<configuration>
...
<settings>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。-->
<setting name="cacheEnabled" value="true"/>
</settings>
...
</configuration>
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
<!--namespace表示Dao的接口-->
<mapper namespace="com.itheima.dao.IUserDao">
<!--让user支持二级缓存-->
<cache/>
...
</mapper>
第三步:让当前的操作支持二级缓存(在select标签中配置),在select标签中添加 useCache="true"属性
<!--根据id查询一个用户-->
<select id="findById" parameterType="Integer" resultType="User" useCache="true">
select * from user where id=#{uid};
</select>
- 编写测试类 SecondLevelCacheTest.java
public class SecondLevelCacheTest {
private InputStream in = null;
private SqlSessionFactory sessionFactory = null;
/**
* 初始化Mybatis
* @throws Exception
*/
@Before //用于在测试方法之前执行
public void init() throws Exception {
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.获取SqlSessionFactor对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
sessionFactory = builder.build(in);
}
/**
* 释放资源
* @throws Exception
*/
@After //用于在测试方法之后执行
public void destroy() throws Exception {
if (in != null)
in.close();
}
/**
* 测试二级缓存
*/
@Test
public void testFirstLevelCache() {
SqlSession sqlSession1 = sessionFactory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(4);
System.out.println(user1);
sqlSession1.close(); //这里关闭SqlSession是为了让一级缓存消失
SqlSession sqlSession2 = sessionFactory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(4);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1 == user2);
}
}
执行测试方法 testFirstLevelCache 控制台输出如下,经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。两次执行findById 方法都拿到了数据,但是两个对象的地址不一样,这是因为,二级缓存,储存的数据时序列化的,当我们去向二级缓存拿数据时在反序列化。所以两个对象的地址不一样。因此,当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
3. MyBatis中的注解开发
3.1 环境搭建
在当前段项目下新建一个名为day04_03annotation_mybatis的Maven的Module,然后创建jdbcConfig.properties、log4j.properties、sqlMapperConfig.xml。然后创建domain类User.java和对应的IUserDao.java,和测试类。该Module的结构如下
3.2单表CRUD操作(代理Dao方式)
sqlMapperConfig.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--上面是mybatis的主配置文件的约束-->
<configuration>
<!--引入外部配置文件-->
<properties resource="jdbcConfig.properties"/>
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类的全限定类名。alias属性指定别名,当指定了别名就不在区分大小写-->
<!--<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>-->
<!--用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不在区分大小写-->
<package name="com.itheima.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--指定带有注解的dao接口所在位置-->
<mappers>
<!--加载这个包下的所有dao接口-->
<package name="com.itheima.dao"/>
<!--<mapper class="com.itheima.dao.IUserDao"/>-->
</mappers>
</configuration>
其中IUserDao.java的内容如下
/**
* 在MyBatis中针对CRUD一共有四个注解
* @Select @Insert @Update @Delete
*/
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},address=#{address},sex=#{sex},birthday=#{birthday}")
void updateUser(User user);
/**
* 根据id删除用户
* @param id
*/
@Delete("delete from user where id=#{id}")
void deleteUser(Integer id);
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id}")
User findById(Integer userId);
/**
* 根据用户名进行模糊查询
* @param name
* @return
*/
//@Select("select * from user where username like #{username};")
//这里的value是固定的写法
@Select("select * from user where username like '%${value}%'")
List<User> findByName(String name);
/**
* 查询用户总数
* @return
*/
@Select("select count(*) from user")
int findTotalUsers();
}
测试类AnnotationCRUDTest.java的内容如下:
public class AnnotationCRUDTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Before
public void init() throws IOException {
//1.获取字节输入流
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.根据字节输入流构建SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
//3.根据SqlSessionFactory生产一个SqlSession
session = factory.openSession();
//4.使用SqlSession获取dao的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws IOException {
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 testUpdateUser() throws ParseException {
User user = new User();
user.setId(8);
user.setUsername("mybatis annotation");
user.setAddress("北京市昌平区");
user.setSex("男");
// JDK 1.8之前
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("1994-01-06");
user.setBirthday(date);
userDao.saveUser(user);
}
@Test
public void testDeleteUser() {
userDao.deleteUser(3);
}
@Test
public void testFindById() {
User user = userDao.findById(4);
System.out.println(user);
}
@Test
public void testFindByName() {
//List<User> users = userDao.findByName("%小%");
//如果dao里的注解 @Select("select * from user where username like '%${value}%'")
// 在传参的时候就不用加%
List<User> users = userDao.findByName("小");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testFindTotal() {
int userCount = userDao.findTotalUsers();
System.out.println(userCount);
}
}
- 注意:
当使用注解进行开发时,但是在配置文件目录下同时包含了同一个dao的映射配置文件,此时不管用不用映射配置文件都会在实际运行时报错。 如下图,如果IUserDao.xml不在 resource/com.itheima.dao的这个包里面就不会报错
3.2 多表查询操作
在当前的项目下新建一个名为day04_04anno_one2many的Maven的Module,把上一节中名为day04_03annotation_mybatis中src下的文件拷贝到当前Module的src目录下。然后拷贝一下配置文件。目录结构如下:
3.2.1 实体类属性名和数据库列名关系映射
- 据库的列名和实体类的属性名不对应的解决办法,修改一下User.java
public class User implements Serializable {
private Integer userId;
private String userName;
private String UserAddress;
private String userSex;
private Date userBirthday;
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) {
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 +
'}';
}
}
然后修改IUserDao.java
public interface IUserDao {
/**
* 查询所有的用户
* @return
*/
@Select("select * from user;")
// 解决数据库的列名和实体类的属性名不对应的办法,这里类似使用映射配置文件中的Results
//Result id值要用的唯一的
@Results(id = "userMap", value = {
//id默认为false,表示不知主键,设置为true表示为主键
@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")
})
List<User> findAll();
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id}")
//这个ResultMap的标准写法,重复使用上面定义的实体类属性和数据库列名的对应关系
// @ResultMap(value = {"userMap"})
//因为注解只有一个value属性,"value="可以省略,又因为数组里只有一个元素,所以"{}"这个大括号也可以省略
@ResultMap("userMap")
User findById(Integer userId);
/**
* 根据用户名进行模糊查询
* @param name
* @return
*/
@Select("select * from user where username like #{username};")
//这里使用上面定义好的数据库类名和实体类属性名的映射
@ResultMap("userMap")
List<User> findByName(String name);
}
测试类AnnotationCRUDTest .java修改如下
public class AnnotationCRUDTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;
@Before
public void init() throws IOException {
//1.获取字节输入流
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.根据字节输入流构建SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
//3.根据SqlSessionFactory生产一个SqlSession
session = factory.openSession();
//4.使用SqlSession获取dao的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws IOException {
session.commit();
session.close();
in.close();
}
@Test
public void testFindAll() {
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testFindById() {
User user = userDao.findById(4);
System.out.println(user);
}
@Test
public void testFindByName() {
List<User> users = userDao.findByName("%小%");
for (User user : users) {
System.out.println(user);
}
}
}
3.2.2 多表查询
一对一查询
一对一查询,我们这里使用查询账户信息的时候同时查询账户的所属用户信息来演示一对一的查询,在原来的基础上新建domain类 Account.java
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。注意在注解中的One,这里的One表示一对一,select表示要使用的查询方法(一定要写全限定类名),fetchType表示加载方式(延时加载/立即加载),当一对一查询的时候用立即加载,一对多(这时候用Many)的时候用延迟加载
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", //使用 uid 查询 把结果封装到 user 属性里
one = @One(select = "com.itheima.dao.IUserDao.findById", fetchType = FetchType.EAGER))
//这里的One表示一对一,select表示要使用的查询方法(一定要写全限定类名),fetchType表示加载方式(延时加载/立即加载),当一对一查询的时候用立即加载,一对多的时候用延迟加载
})
List<Account> findAll();
}
新建一个测试类 AccountTest.java
public class AccountTest {
private InputStream in;
private SqlSessionFactory factory;
private SqlSession session;
private IAccountDao accountDao;
@Before
public void init() throws IOException {
//1.获取字节输入流
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.根据字节输入流构建SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
//3.根据SqlSessionFactory生产一个SqlSession
session = factory.openSession();
//4.使用SqlSession获取dao的代理对象
accountDao = session.getMapper(IAccountDao.class);
}
@After
public void destroy() throws IOException {
session.commit();
session.close();
in.close();
}
@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());
}
}
}
然后运行测试类中的 testFindAll 方法,数据封装到了user里。
一对多查询
一对多的查询,我们这里使用查询用户信息的时候查询用户所拥有的账户信息。在原来 User.java类的基础上新增 accounts 属性,并且添加其 get 和 set 方法。
public class User implements Serializable {
...
//一对多的关系映射:一个用户对应多个账户
private List<Account> accounts;
public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
...
}
然后在用 IAccountDao.java中新增一个通过用户id查询其所拥有的账户信息的方法。这个方法也用于,在查询用户信息的时候同时查询用户所拥有的账户信息。
public interface IAccountDao {
...
/**
* 根据有户id查询账户
* @return 用户所拥有的账户信息
*/
@Select("select * from account where uid=#{id};")
List<Account> findAccountByUid(Integer uid);
}
在测试类 AccountTest.java 新增对 findAccountByUid 方法的测试方法。这个测试方法就是测试testFindByUid的,不涉及一对多的查询。
public class AccountTest {
...
@Test
public void testFindByUid() {
List<Account> accounts = accountDao.findAccountByUid(1);
for (Account account : accounts) {
System.out.println("------------------------ 每个账户的信息 ------------------------");
System.out.println(account);
}
}
}
下面修改 IUserDao.java里 findAll 方法的注解。因为是一对多查询,所以在注解里使用 Many 而不是 One,这里是一对多查询,所以使用延时加载。
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.itheima.dao.IAccountDao.findAccountByUid", fetchType = FetchType.LAZY))
})
List<User> findAll();
...
}
最后修改 对 IUserDao 的测试类 AnnotationCRUDTest.java 中的 testFindAll 方法。
@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的主配置里配置, 因为我们使用的注解会自动打开延时加载。<!--配置参数--> <settings> <!--开启MyBatis支持延迟加载的全局开关--> <setting name="lazyLoadingEnabled" value="true"/> <!--开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载--> <setting name="aggressiveLazyLoading" value="true"/> </settings>
运行测试方法 testFindAll 控制台输出如下,可以看出只查询了用户信息没有查询账户信息,实现了延时加载。
4 注解开发使用二级缓存
无论是使用注解开发还是使用xml开发一级缓存都是不用配置的自动就是有的。比如我们修改一下 测试类 AnnotationCRUDTest.java中的 testFindById 方法如下:
@Test
public void testFindById() {
User user = userDao.findById(4);
System.out.println(user);
User user2 = userDao.findById(4);
System.out.println(user2);
System.out.println(user == user2);
}
运行一下输出如下,只查询了一次user表,并且两个对象是同一个。
新建一个测试类 SecondLevelCatchTest.java
public class SecondLevelCatchTest {
private InputStream in;
private SqlSessionFactory factory;
@Before
public void init() throws IOException {
in = Resources.getResourceAsStream("SqlMapperConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
}
@After
public void destroy() throws IOException {
in.close();
}
@Test
public void testFindById() {
SqlSession session = factory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
User user = userDao.findById(4);
System.out.println(user);
session.close();//关闭session去释放缓存。
SqlSession session1 = factory.openSession();
IUserDao userDao1 = session1.getMapper(IUserDao.class);
User user1 = userDao1.findById(4);
System.out.println(user1);
session1.close();
}
}
运行测试方法 testFindById 输出如下,可以看出是查询了两次。
设置开启二级缓存
设置在MyBatis的主配置文件中添加开启二级缓存的配置。
...
<!--设置开启二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
...
然后在要使用二缓存的dao接口上添加注解 @CacheNamespace(blocking = true),如:修改一下IUserDao.java
@CacheNamespace(blocking = true) //设置开启二级缓存
public interface IUserDao {
...
}
然后在运行一下测试类的测试方法 testFindById 输出如下,只查询了一次user表,说明使用了二级缓存。
参考: