mybatis学习-day02
mybatis-day02
-
搭建开发环境
- 准备工作:
- 导入依赖:mysql驱动,mybatis,log4j, junit
- 准备JavaBean:建议属性名和表字段名保持一致
- 使用mybatis
- 创建映射器:dao层的接口,接口里写方法
- 创建映射文件:在映射文件里给每个方法增加配置,主要配置sql语句
- 提供全局配置文件:
- typeAliases类型别名
- 数据库环境
- 映射器mappers
- 提供日志配置文件:log4j.properties放到类路径下(普通工程src是类路径;Maven工程src\main\java,和src\main\resources是类路径)
- 功能测试
- 准备工作:
-
代理方式CURD
-
增加:
insert
标签- id属性:写对应的方法名
- parameterType属性:参数类型,写全限定类名 或者 别名
- 在insert标签里边写sql语句
<selectKey keyProperty="id" resultType="int" order="AFTER"> select last_insert_id() </selectKey> insert into tbl () values (#{JavaBean的属性名},...)
-
修改:
update
标签- id属性:写对应的方法名
- parameterType属性:参数类型,写全限定类名 或者 别名
-
删除:
delete
标签- id属性:写对应的方法名
- parameterType属性:参数类型,写全限定类名 或者 别名
-
查询:
select
标签- id属性:写对应的方法名
- parameterType属性:参数类型,写全限定类名 或者 别名
- resultType属性:自动映射,Mybatis会针对JavaBean属性名和字段相同的 进行自动映射
- resultMap属性:手动映射,手动配置JavaBean属性名和字段名的映射关系
-
-
parameterType
- 可以有一个参数:
- 假如参数是简单类型,
#{随意写}
- 假如参数是POJO,
#{pojo的属性名}
- 假如参数是Map,
#{map的key}
- 假如参数是List,
#{list[索引]}
#{collection[索引]}
- 假如参数是数组,
#{array[索引]}
- 假如参数是简单类型,
- 可以有多个参数
- 没有使用@Param配置参数名称
#{arg0}, #{arg1}, ...
#{param1}, #{param2},....
- 假如使用了@Param配置了参数名称
#{配置的参数名称}
#{param1}, #{param2},....
- 没有使用@Param配置参数名称
- 可以有一个参数:
一、SQL深入-动态sql【重点】
我们在前边的学习过程中,使用的SQL语句都非常简单。而在实际业务开发中,我们的SQL语句通常是动态拼接而成的,比如:条件搜索功能的SQL语句。
在Mybatis中,SQL语句是写在映射配置的XML文件中的。Mybatis提供了一些XML的标签,用来实现动态SQL的拼接。
常用的标签有:
<if></if>
:用来进行判断,相当于Java里的if判断<where></where>
:通常和if配合,用来代替SQL语句中的where 1=1
<foreach></foreach>
:用来遍历一个集合,把集合里的内容拼接到SQL语句中。例如拼接:in (value1, value2, ...)
<sql></sql>
:用于定义sql片段,达到重复使用的目的
准备环境
我们以对user表的操作为例,演示这些标签的用法。先准备Mybatis的环境如下
-
创建java项目,导入jar包
-
创建JavaBean实体类:User
-
创建映射器接口UserDao
-
创建映射文件UserDao.xml
-
创建Mybatis的核心配置文件,配置好类型别名和映射器
-
准备log4j日志配置文件
-
准备好单元测试类
public class MybatisSqlTest { private InputStream is; private SqlSession session; private UserDao dao; @Before public void init() throws IOException { is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); session = factory.openSession(); dao = session.getMapper(UserDao.class); } @After public void destory() throws IOException { session.close(); is.close(); } }
<if>
标签:
<if test="判断条件,使用OGNL表达式进行判断">
SQL语句内容, 如果判断为true,这里的SQL语句就会进行拼接
</if>
需求描述
-
根据用户的名称和性别搜索用户信息。
-
把搜索条件放到User对象里,传递给SQL语句
需求实现
1) 在映射器UserDao中增加方法
//根据user搜索用户信息
List<User> search(User user);
2) 在映射文件UserDao.xml中配置
- 在if标签的test属性中,直接写OGNL表达式,从parameterType中取值进行判断,不需要加
#{}
或者${}
<select id="search" parameterType="user" resultType="user">
select * from user where 1=1
<if test="username != null and username.length() > 0">
and username like #{username}
</if>
<if test="sex != null and username.length() > 0">
and sex = #{sex}
</if>
</select>
3) 在单元测试类中编写测试代码
@Test
public void testSearch(){
User user = new User();
user.setUsername("%王%");
user.setSex("男");
List<User> users = dao.search(user);
for (User u : users) {
System.out.println(u);
}
}
小结
<where>
标签
在刚刚的练习的SQL语句中,我们写了where 1=1
。如果不写的话,SQL语句会出现语法错误。Mybatis提供了一种代替where 1=1
的技术:<where></where>
标签。
-
<where>
标签代替了where 1=1
-
<where>
标签内拼接的SQL没有变化,每个if的SQL中都有and
-
<where>
标签会自动处理掉第一条件里前边的and
,以保证SQL语法正确
需求描述
使用<where></where>
标签代替where 1=1
需求实现
- 只需要修改一下配置文件:
<select id="search" parameterType="user" resultType="user">
select * from user
<where>
<if test="username != null and username.length() > 0">
and username like #{username}
</if>
<if test="sex != null and username.length() > 0">
and sex = #{sex}
</if>
</where>
</select>
- 再次运行测试代码,查看结果仍然正常
小结
<foreach>
标签
foreach标签,通常用于循环遍历一个集合,把集合的内容拼接到SQL语句中。例如,我们要根据多个id查询用户信息,SQL语句:
select * from user where id = 1 or id = 2 or id = 3;
select * from user where id in (1, 2, 3);
假如我们传参了id的集合,那么在映射文件中,如何遍历集合拼接SQL语句呢?可以使用foreach
标签实现。
<!--
foreach标签:
属性:
collection:被循环遍历的对象,使用OGNL表达式获取,注意不要加#{}
open:循环之前,拼接的SQL语句的开始部分
item:定义变量名,代表被循环遍历中每个元素,生成的变量名
separator:分隔符
close:循环之后,拼接SQL语句的结束部分
标签体:
使用#{OGNL}表达式,获取到被循环遍历对象中的每个元素
-->
<foreach collection="" open="id in(" item="id" separator="," close=")">
#{id}
</foreach>
id in(41,42,44,45)
需求描述
QueryVO中有一个属性ids, 是id值的集合。根据QueryVO中的ids查询用户列表。
QueryVO类如下:
public class QueryVO {
private Integer[] ids;
public Integer[] getIds() {
return ids;
}
public void setIds(Integer[] ids) {
this.ids = ids;
}
}
需求实现
1) 在映射器接口UserDao中增加方法
List<User> findByIds(QueryVO vo);
2) 在映射文件UserDao.xml中添加statement
<!--在核心配置文件中已经使用package配置了类型别名-->
<select id="findByIds" resultType="user" parameterType="queryvo">
select * from user
<where>
<foreach collection="ids" open="and id in (" item="id" separator="," close=")">
#{id}
</foreach>
</where>
</select>
3) 在单元测试类中编写测试代码
@Test
public void testFindUserByIdsQueryVO(){
QueryVO vo = new QueryVO();
vo.setIds(new Integer[]{41, 42});
List<User> userList = dao.findByIds(vo);
for (User user : userList) {
System.out.println(user);
}
}
小结
<foreach collection="被循环遍历的集合" item="每一个元素的变量名" separator="分隔符" open="前缀" close="后缀">
#{每一个元素的变量名}
</foreach>
拼接的结果是:前缀 + 用指定分隔符拼接的字符串 + 后缀
<sql>
标签
在映射文件中,我们发现有很多SQL片段是重复的。Mybatis提供了一个<sql>
标签,把重复的SQL片段抽取出来,可以重复使用。
定义SQL片段:
<sql id="唯一标识">sql语句片段</sql>
引用SQL片段:
<include refid="sql片段的id"></include>
扩展:
如果想要引入其它映射文件中的sql片段,那么
<include>
标签的refid的值,需要在sql片段的id前指定namespace。例如:
<include refid="com.guang.dao.RoleDao.selectRole"></include>
表示引入了namespace为
com.guang.dao.RoleDao
的映射文件中id为selectRole
的sql片段
需求描述
在查询用户的SQL中,需要重复编写:select * from user
。把这部分SQL提取成SQL片段以重复使用
需求实现
1) 在映射文件UserDao.xml中定义SQL片段
<sql id="selectUser">
select * from user
</sql>
2) 在映射文件UserDao.xml中使用SQL片段
<select id="findByIds" resultType="user" parameterType="queryvo">
<!-- refid属性:要引用的sql片段的id -->
<include refid="selectUser"></include>
<where>
<foreach collection="ids" open="and id in (" item="id" separator="," close=")">
#{id}
</foreach>
</where>
</select>
小结
小结
<select id="xxx" parameterType="QueryVO" resultType="user">
<include refid="yyy"/>
<where>
<!-- 循环集合ids -->
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" item="id" separator="," open="and id in(" close=")">
#{id}
</foreach>
</if>
<!-- user里的username和sex -->
<if test="user != null">
<if test="user.username != null and user.username.length()>0">
and username like #{user.username}
</if>
<if test="user.sex != null and user.sex.length() > 0">
and sex = #{user.sex}
</if>
</if>
</where>
</select>
<sql id="yyy">select * from user</sql>
二、Mybatis的缓存
准备环境,略。
我们以 根据主键查询User为例,演示缓存的效果
注意:User类不要重写toString()方法,我们需要打印User对象的地址
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//get/set方法......
}
一级缓存
什么是一级缓存
什么是一级缓存?
- 一级缓存,也叫本地缓存(
localCache
),是SqlSession
对象提供的缓存,默认是开启的 - 执行一次查询之后,查询的结果会被缓存到SqlSession中。
- 再次查询同样的数据,Mybatis会优先从缓存中查找;如果找到了,就不再查询数据库。
什么情况下一级缓存会失效?
- 当
sqlSession
执行了修改、添加、删除时 - 当
sqlSession
提交了事务commit()
或回滚rollback()
时 - 当手动清理缓存
sqlSession.clearCache()
时 - 当SqlSession对象关闭
sqlSession.close()
时
拓展:一级缓存能否关闭?可以,但通常不需要
- 在全局配置文件中添加设置项
localCacheScope
的值为STATEMENT
即可
一级缓存效果演示
/**
* Mybatis缓存效果演示
*/
public class MybatisCacheTest {
private InputStream is;
private SqlSession session;
private UserDao dao;
/**
* 测试 Mybatis的一级缓存:
* SQL语句执行了一次、输出user1和user2的地址是相同的
* 说明Mybatis使用了缓存
*/
@Test
public void testLevel1Cache(){
User user1 = dao.findUserById(41);
System.out.println(user1);
User user2 = dao.findUserById(41);
System.out.println(user2);
}
/**
* 测试 清除Mybatis的一级缓存
* 两次打印的User地址不同,执行了两次SQL语句
* SqlSession的修改、添加、删除、commit()、clearCache()、close()都会清除一级缓存
*/
@Test
public void testClearLevel1Cache(){
User user1 = dao.findUserById(41);
session.clearCache();
User user2 = dao.findUserById(41);
System.out.println(user1 == user2);
}
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
session = factory.openSession();
dao = session.getMapper(UserDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}
一级缓存的源码分析
SqlSession
接口:面向用户的接口,提供了让用户调用的方法:selectList, selectOne, insert, update, delete...。 实现类DefaultSqlSession
Executor
接口:每次操作数据库,本质都是由这个Executor来执行的。每个SqlSession里都有自己的一个Executor对象Cache
接口:缓存接口
二级缓存
什么是二级缓存
什么是二级缓存?
-
是映射器级别的缓存,同一映射器Mapper共享缓存。
-
当
SqlSession
查询了一条数据后,会把数据存储到SqlSession
的一级缓存中 -
当这个
SqlSession
关闭或提交时,Mybatis可以把一级缓存中的数据,转存到二级缓存中 -
当有新的
SqlSession
对象要获取 数据时,优先从二级缓存中查找
什么情况下二级缓存会失效?
- 默认情况下,执行增删改之后,一级缓存和二级缓存都会清空
- 默认情况下,查询数据,不会清除缓存
注意事项:
- 如果要使用二级缓存,要求JavaBean实现序列化接口
Serializable
- 二级缓存需要手动开启
二级缓存效果演示
1) 修改全局配置文件,开启全局的二级缓存开关
<settings>
<!-- 增加此配置项,启动二级缓存(默认值就是true,但是仍然建议配置上) -->
<setting name="cacheEnabled" value="true"/>
</settings>
2) 修改映射文件UserDao.xml,让映射器支持二级缓存
<mapper namespace="com.guang.dao.UserDao">
<!-- 把cache标签加到映射文件 mapper标签里 -->
<cache/>
......
</mapper>
3) 修改映射文件UserDao中的findById,让此方法(statement)支持二级缓存
<!-- 如果statement的标签上,设置有的useCache="true",表示此方法要使用二级缓存 -->
<select id="findById" parameterType="int" resultType="user" useCache="true">
select * from user where id = #{id}
</select>
4) 修改JavaBean:User
注意:如果要使用二级缓存,那么JavaBean需要实现Serializable接口
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//get/set......
}
5) 编写测试代码
/**
* Mybatis二级缓存效果演示
*/
public class MybatisLevel2CacheTest {
private InputStream is;
private SqlSessionFactory factory;
/**
* 测试二级缓存。
* 测试结果:
* 虽然输出的user1和user2地址不同,但是SQL语句只执行了一次,说明第二次用了缓存。
* Mybatis的二级缓存,保存的不是JavaBean对象,而是散列的数据。
* 当要获取缓存时,把这些数据重新组装成一个JavaBean对象,所以地址不同
*/
@Test
public void testLevel2Cache(){
SqlSession session1 = factory.openSession();
UserDao dao1 = session1.getMapper(UserDao.class);
User user1 = dao1.findUserById(41);
session1.close();
SqlSession session2 = factory.openSession();
UserDao dao2 = session2.getMapper(UserDao.class);
User user2 = dao2.findUserById(41);
session2.close();
System.out.println(user1==user2);
}
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
}
@After
public void destory() throws IOException {
is.close();
}
}
二级缓存原理
三、多表关联查询【重点】
-
多表关系:
- 一对一:一对一通常会合并成一张表,但是:
- 如果一张表太大,把常用字段放在一张表里,不常用字段放在另外一张表
- 一对多:用户和订单, 部门和员工
- 使用外键维护数据的完整性和一致性
- 多对多:学生和课程,订单和商品,老师和学生
- 通过中间关系表,来维护多对多的关系
- 一对一:一对一通常会合并成一张表,但是:
-
多表查询语法:
- 内连接:查询表之间必定有关联的数据,无关数据是会被剔除的
# 显式内连接 select * from table1 inner join table2 on 表关联条件 # 隐式内连接 select * from table1, table2 where 表关联条件
- 外连接:查询一张表的全部数据,及另一张表的关联的数据
# 左外连接:查左表的全部数据,及右表的关联数据 select * from 左表 left join 右表 on 表关联条件 # 右外连接:查右表的全部数据,及左表的关联数据 select * from 左表 right join 右表 on 表关联条件
- 子查询:查询嵌套的技巧
# 子查询是一个值 # 查询帐号1所属的用户 select * from user where id = (select uid from account where id = 1) # 子查询是一个集合 # 查询帐号余额大于1000的用户 select * from user where id in(select uid from account where money > 1000) # 子查询是一张虚拟表。拿虚拟表和其它表关联查询 # 查询帐号余额大于1000的用户和帐号信息 select * from user u right join (select * from account where money > 1000) t on u.id = t.uid select * from user u right join account a on u.id = a.uid where a.money > 1000
准备工作
-
创建java项目,导入依赖
-
分别创建好user表和account表的实体类:User和Account
-
在dao中创建映射器接口AccountDao和UserDao
-
创建映射器的配置文件AccountDao.xml和UserDao.xml
-
创建Mybatis核心配置文件,配置好类型别名和映射器
-
准备日志配置文件
log4j.properties
-
编写好单元测试类
public class MybatisMultipleTest {
private InputStream is;
private SqlSession session;
private AccountDao accountDao;
private UserDao userDao;
@Before
public void init() throws IOException {
is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
session = factory.openSession();
accountDao = session.getMapper(AccountDao.class);
userDao = session.getMapper(UserDao.class);
}
@After
public void destory() throws IOException {
session.close();
is.close();
}
}
一对一(多对一)关联查询
- 查询所有帐户表信息,及其关联的用户信息
方案一:类继承方式(不推荐)
创建新的JavaBean用于封装查询结果,定义与所有字段对应的属性,可以使用继承的方式来减少代码量。
例如:
-
创建UserAccount类,定义user表对应的属性,然后继承Account类
-
创建UserAccount类,定义account表对应的属性,然后继承User类
1) 创建JavaBean:UserAccount
public class UserAccount extends Account {
private String username;
private String address;
//get/set...
//toString...
}
2) 在映射器AccountDao中增加方法
//查询所有帐号,及其关联的用户信息--类继承的方式
List<UserAccount> queryAllAccounts1();
3) 在映射文件AccountDao.xml中增加配置
- 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllAccounts1" resultType="userAccount">
select a.*, u.username, u.address from account a left join user u on a.uid = u.id
</select>
4) 编写测试代码
@Test
public void testQueryAllAccounts(){
List<UserAccount> userAccounts = accountDao.queryAllAccounts1();
for (UserAccount userAccount : userAccounts) {
System.out.println(userAccount);
}
}
方案二:类引用方式(推荐)
JavaBean中要有 关联JavaBean的引用。例如:
- 在Account中增加一个属性user,指向User对象。
- 把查询结果集中,帐号信息封装到Account中
- 把查询结果集中,用户信息封装到Account的User中
1) 修改JavaBean:Account类
- 注意:Account中要有User的引用
public class Account {
private Integer id;
private Integer uid;
private Double money;
private User user;
//get/set...
//toString...
}
2) 在映射器AccountDao中增加方法
//查询所有帐号,及其关联的用户信息-类引用方式
List<Account> queryAllAccounts2();
3) 在映射文件AccountDao.xml中增加配置
- 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllAccounts2" resultMap="AccountUserMap">
SELECT a.id aid, a.uid uid, a.money money, u.* FROM account a LEFT JOIN USER u ON a.uid = u.id
</select>
<resultMap id="AccountUserMap" type="account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--
association:用于把结果集中某些列的数据,封装到JavaBean中关联的一个对象上。用于一对一情形
property:把数据封装到哪个属性关联的对象上
javaType:关联的对象是什么类型的。是com.guang.domain.User,这里使用了别名
-->
<association property="user" javaType="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
</resultMap>
4) 编写测试代码
@Test
public void testQueryAllAccounts2(){
List<Account> accounts = accountDao.queryAllAccounts2();
for (Account account : accounts) {
System.out.println(account);
}
}
方案三:查询嵌套方式(推荐)
我们可以把多表关联查询,拆分成两步:
- 先查询帐号的信息,封装到Account里
- 再查询每个帐号关联的用户信息,封装到Account内的User对象里(我们配置好,Mybatis自动调用)
1) 修改JavaBean:Account类
- Account类里要有一个成员变量:User(同方案二的JavaBean)
2) 在映射器AccountDao中增加方法
List<Account> queryAllAccounts3();
3) 在映射文件AccountDao.xml中增加配置
<select id="queryAllAccounts3" resultMap="accountMap">
select * from account
</select>
<resultMap id="accountMap" type="Account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--
根据用户的id查询用户对象
调用方法com.guang.dao.UserDao.findById,把uid字段值传递过去,得到对应User对象
-->
<association property="user" javaType="User"
select="com.guang.dao.UserDao.findById"
column="uid"/>
</resultMap>
4) 在映射器UserDao中增加方法
User findById(Integer id);
5) 在映射文件UserDao.xml中增加配置
<select id="findById" resultType="User">
select * from user where id = #{uid}
</select>
6) 编写测试代码
@Test
public void testOne() throws IOException {
List<Account> accounts = accountDao.queryAllAccounts3();
for (Account account : accounts) {
System.out.println(account);
}
}
小结
对一关联查询注意事项:
- JavaBean:Account关联一个User,所以Account里应该有User类型的成员变量
- sql语句:多表查询语句,注意不能有重名列,可以起别名保证所有列名不重复
- 结果集要使用resultMap进行手动映射
使用多表查询语句,手动配置映射关系
<select id="queryAllAccounts" resultMap="accountMap">
select a.id aid, a.uid, a.money, u.* from account a left join user u on u.id = a.uid
</select>
<resultMap id="accountMap" type="Account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--<result property="user.username" column="username"/>-->
<!--
association标签:用于把数据封装到关联的一个JavaBean对象里
property:属性名。哪个属性是关联的JavaBean
javaType:关联的JavaBean是什么类型的,可以写全限定类名或别名
-->
<association property="user" javaType="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
</resultMap>
把多表查询拆分成多步查询
- 查询所有帐号。把帐号的数据封装到Account对象里
- Account里需要的那个User:调用另外一个方法,查询当前帐号关联的那个User对象
<select id="queryAllAccounts2" resultMap="accountMap2">
select * from account
</select>
<resultMap id="accountMap2" type="Account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!-- 调用另外一个方法,得到关联的User对象。被调用的方法,必须提供好 -->
<association property="user" javaType="User" select="com.guang.dao.UserDao.findById" column="uid"/>
</resultMap>
- 被调用的方法
public interface UserDao {
User findById(Integer id);
}
<select id="findById" resultType="User">
select * from user where id = #{id}
</select>
一对多(多对多)关联查询
- 查询所有用户(user)信息,以及每个用户拥有的所有帐号(account)信息
类引用方式
- JavaBean中要有关联JavaBean的集合。
- 例如:在User中增加一个属性accounts,类型是
List<Account>
- 把查询结果集里,用户的信息封装到User中
- 把查询结果集里,帐号的信息封装到User的accounts中
1) 修改JavaBean:User类
- User类中要有
List<Account>
,用于保存用户拥有的帐号信息集合
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
/**增加属性:account的集合*/
private List<Account> accounts;
//get/set...
//toString...
}
2) 在映射器UserDao中增加方法
List<User> queryAllUsers();
3) 在映射文件UserDao.xml中增加配置
- 注意:SQL语句查询结果集中,不能有重名列。如果有,给列起别名保证没有重名列
<select id="queryAllUsers" resultMap="userAccountsMap">
SELECT a.id aid, a.uid uid, a.money money, u.* FROM USER u LEFT JOIN account a
ON u.id = a.uid
</select>
<resultMap id="userAccountsMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--
collection:用于封装JavaBean中某一属性关联的集合,用于一对多情形
property:封装哪个属性关联的集合
ofType:集合中的数据类型是什么。这里是com.guang.domain.Account,使用了别名
-->
<collection property="accounts" ofType="account">
<id property="id" column="aid"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
</collection>
</resultMap>
4) 编写测试代码
@Test
public void testQueryAllUsers(){
List<User> users = userDao.queryAllUsers();
for (User user : users) {
System.out.println(user);
}
}
查询嵌套方式
1) 修改JavaBean:User类
- 同类引用方式里的JavaBean相同,略
2) 在映射器UserDao中增加方法
List<User> queryAllUser2();
3) 在映射文件UserDao.xml中增加配置
<select id="queryAllUser2" resultMap="userMap">
select * from user
</select>
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="accounts" ofType="Account"
select="com.guang.dao.AccountDao.findByUid" column="id"/>
</resultMap>
4) 在映射器AccountDao中增加方法
List<Account> findByUid(Integer uid);
5) 在映射文件AccountDao.xml中增加配置
<select id="findByUid" resultType="Account">
select * from account where uid = #{uid}
</select>
6) 编写测试代码
@Test
public void testMany() throws IOException {
UserDao userDao = session.getMapper(UserDao.class);
List<User> userList = userDao.queryAllUser2();
for (User user : userList) {
System.out.println(user);
}
小结
练习-多对多关联查询
-
现有用户表(user)和角色表(role),是多对多关系。有中间关系表
user_role
-
练习:
-
查询所有用户,及关联的角色集合
-
查询所有角色,及关联的用户集合
-
准备工作
-
创建Maven的java项目,配置好坐标,引入Mybatis的依赖(略)
-
创建用户信息和角色信息的实体类
注意:User中要有Role的集合; Role中要有User的集合
public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Role> roles; //get/set方法...... //toString方法...... }
public class Role { private Integer id; private String roleName; private String roleDesc; private List<User> users; //get/set方法...... //toString方法...... }
-
创建映射器接口UserDao和RoleDao(准备好备用,暂时不需要加方法)
-
创建映射文件UserDao.xml和RoleDao.xml
-
创建Mybatis的核心配置文件,配置好别名和映射器
-
准备单元测试类(准备好备用)
public class MybatisMany2ManyTest { private InputStream is; private SqlSession session; private UserDao userDao; private RoleDao roleDao; @Before public void init() throws IOException { is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); session = factory.openSession(); userDao = session.getMapper(UserDao.class); roleDao = session.getMapper(RoleDao.class); } @After public void destory() throws IOException { session.close(); is.close(); } }
练习1:查询所有用户,及关联的角色集合
1) 在映射器接口UserDao中增加方法
//查询所有用户信息,及其关联的角色集合
List<User> queryAllUsers();
2) 在映射文件UserDao.xml中增加statement
<select id="queryAllUsers" resultMap="userRolesMap">
SELECT u.*, r.id rid, r.role_name roleName, r.role_desc roleDesc
FROM USER u LEFT JOIN user_role ur ON u.id = ur.uid
LEFT JOIN role r ON ur.rid = r.id
</select>
<resultMap id="userRolesMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<!--
collection:用于封装JavaBean里关联的角色集合
-->
<collection property="roles" ofType="role">
<id property="id" column="rid"/>
<result property="roleName" column="roleName"/>
<result property="roleDesc" column="roleDesc"/>
</collection>
</resultMap>
3) 在单元测试类中编写测试代码
/**
* 查询所有用户信息,及其关联的角色信息集合
*/
@Test
public void testQueryAllUsers(){
List<User> users = userDao.queryAllUsers();
for (User user : users) {
System.out.println(user);
}
}
练习2:查询所有角色,及关联的用户集合
1) 在映射器接口RoleDao中增加方法
//查询所有角色信息,及其关联的用户集合
List<Role> queryAllRoles();
2) 在映射文件RoleDao.xml中增加statement
<select id="queryAllRoles" resultMap="roleUsersMap">
SELECT r.id rid, r.role_name roleName, r.role_desc roleDesc, u.*
FROM role r LEFT JOIN user_role ur ON r.id = ur.rid
LEFT JOIN USER u ON ur.uid = u.id
</select>
<resultMap id="roleUsersMap" type="role">
<id property="id" column="rid"/>
<result property="roleName" column="roleName"/>
<result property="roleDesc" column="roleDesc"/>
<collection property="users" ofType="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</collection>
</resultMap>
3) 在单元测试类中编写测试代码
/**
* 查询所有角色信息,及其关联的用户信息集合
*/
@Test
public void testQueryAllRoles(){
List<Role> roles = roleDao.queryAllRoles();
for (Role role : roles) {
System.out.println(role);
}
}
小结
- 只要是多表关联,就需要修改JavaBean
- 如果是关联一个,Account里要有一个User
- 如果是关联多个,User里要有一个
List<Account>
- sql语句:查询结果集一定不能有重名列,通过起别名保证所有列不重复
- 在映射文件里,使用resultMap手动映射
<select id="" resultMap="yyy">
xxxsqlxxxx
</select>
<resultMap id="yyy" type="">
<!-- 如果关联一个 -->
<association property="" javaType="">
</association>
<association property="" javaType="" select="" column/>
<!-- 如果关联多个 -->
<collection property="" ofType="">
</collection>
<collection property="" ofType="" select="" column/>
</resultMap>
四、Mybatis的延迟加载【重点】
在多表关联查询时,比如查询用户信息,及其关联的帐号信息,在查询用户时就直接把帐号信息也一并查询出来了。但是在实际开发中,并不是每次都需要立即使用帐号信息,这时候,就可以使用延迟加载策略了。
什么是延迟加载
立即加载
- 不管数据是否需要使用,只要调用了方法,就立即发起查询。
- 比如:查询帐号,得到关联的用户;查询用户,得到关联的帐号
延迟加载
- 延迟加载,也叫按需加载,或者叫懒加载。
- 只有当真正使用到数据的时候,才发起查询。不使用不发起查询
- 比如:查询用户信息,不使用accounts的时候,不查询帐号的数据;只有当使用了用户的accounts,Mybatis再发起查询帐号的信息
- 好处:先从单表查询,需要使用关联数据时,才进行关联数据的查询。
- 单表查询语句简单,查询速度比多表关联查询快
- 内存占用小
- 坏处:当需要使用数据时才会执行SQL。这样大批量的SQL执行的情况下,会造成查询等待时间比较长
延迟加载的使用场景
- 一对一(多对一),通常不使用延迟加载(建议)。比如:查询帐号,关联加载用户信息
- 一对多(多对多),通常使用延迟加载(建议)。比如:查询用户,关联加载帐号信息
延迟加载的实现
无论是对一,还是对多,如果想要实现延迟加载,只要在查询嵌套的基础上,再多一步:
- 在全局配置文件中,开启懒加载
对一的延迟加载
查询帐号信息,及其关联的用户信息。使用懒加载的方式实现。
步骤:
-
使用查询嵌套的方式,查询帐号及关联的用户信息
-
在核心配置文件中,开启懒加载
1. 用查询嵌套方式,查询帐号及关联的用户
1) 在映射器AccountDao中增加方法
List<Account> queryAllAccounts();
2) 在映射文件AccountDao.xml中增加配置
<select id="queryAllAccounts" resultMap="accountLazyUser">
select * from account
</select>
<resultMap id="accountLazyUser" type="account">
<id property="id" column="id"/>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--
association标签:用于封装关联的JavaBean对象
select:调用哪个statement,懒加载 得到关联的JavaBean对象
column:调用statement时,需要传递的参数值,从哪个字段中取出
-->
<association property="user" javaType="user"
column="uid" select="com.guang.dao.UserDao.findById"/>
</resultMap>
3) 在映射器UserDao中增加方法
User findById(Integer id);
4) 在映射文件UserDao.xml中增加配置
<select id="findById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>
2. 在核心配置文件中开启懒加载
<!-- 把settings标签放到typeAliases之前 -->
<settings>
<!-- 启动延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 禁用积极加载:使用按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3. 编写测试代码
/**
* 测试一对一实现懒加载:查询帐号,及其关联的一个用户。
*/
@Test
public void testQueryAllAccounts(){
List<Account> accounts = accountDao.queryAllAccounts();
for (Account account : accounts) {
System.out.println(account.getId()+", " + account.getUid() + ", " +account.getMoney());
//执行下面这行代码,才会发起查询user的SQL语句
System.out.println(account.getUser());
}
}
对多的延迟加载
查询用户信息,及其关联的帐号信息集合。使用延迟加载实现。
实现步骤:
-
使用查询嵌套的方式,查询用户信息,及关联的帐号集合
-
在核心配置文件中,开启懒加载
1. 用查询嵌套方式,查询用户及关联的帐号
1) 在映射器UserDao中增加方法
List<User> queryAllUsers();
2) 在映射文件UserDao.xml中增加statement
<select id="queryAllUsers" resultMap="userLazyAccounts">
select * from user
</select>
<resultMap id="userLazyAccounts" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="accounts" ofType="account"
column="id" select="com.guang.dao.AccountDao.findByUid"/>
</resultMap>
3) 在映射器AccountDao中增加方法
List<Account> findByUid(Integer uid);
4) 在映射文件AccountDao.xml中增加statement
<select id="findByUid" parameterType="int" resultType="account">
select * from account where uid = #{id}
</select>
2. 在核心配置文件中开启懒加载
<!-- 把settings标签放到typeAliases之前 -->
<settings>
<!-- 启动延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 禁用积极加载:使用按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3. 编写测试代码
/**
* 测试一对多实现懒加载:查询用户,及其关联的帐号集合
*/
@Test
public void testQueryAllUsers(){
List<User> users = userDao.queryAllUsers();
for (User user : users) {
System.out.println(user.getUsername()+", " + user.getSex());
//执行页面这行代码,才会发起查询account的SQL语句
System.out.println(user.getAccounts());
}
}
小结
- 在查询嵌套方式的基础上,开启懒加载的全局开关
<settings>
<!--开启懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭积极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
五、Mybatis的注解开发
Mybatis也支持注解开发。但是需要明确的是,Mybatis仅仅是把映射文件 使用注解代替了;而Mybatis的核心配置文件,仍然是xml配置。
1 准备环境
-
创建Java项目,导入jar包
-
创建JavaBean:User和Account
public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Account> accounts; //get/set... //toString... }
public class Account { private Integer id; private Integer uid; private Double money; private User user; //get/set... //toString... }
-
创建映射器接口UserDao和AccountDao,备用
-
准备Mybatis的核心配置文件,配置好别名和映射器
<?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"> <configuration> <typeAliases> <package name="com.guang.domain"/> </typeAliases> <environments default="mysql_mybatis"> <environment id="mysql_mybatis"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <package name="com.guang.dao"/> </mappers> </configuration>
-
准备好单元测试类备用
/** * Mybatis的注解开发功能测试--简单的CURD操作 */ public class MybatisAnnotationTest { private InputStream is; private SqlSession session; private UserDao userDao; private AccountDao accountDao; @Before public void init() throws IOException { is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); session = factory.openSession(); userDao = session.getMapper(UserDao.class); accountDao = session.getMapper(AccountDao.class); } @After public void destory() throws IOException { session.close(); is.close(); } }
2. 常用注解介绍
- @Select:相当于映射文件里的select标签:用于配置查询方法的语句
- @Insert:相当于映射文件里的insert标签
- @SelectKey:相当于映射文件里的selectKey标签,用于添加数据后获取最新的主键值
- @Update:相当于映射文件里的update标签
- @Delete:相当于映射文件里的delete标签
- @Results:相当于映射文件里的resultMap标签
- @Result:相当于映射文件里的result标签,和@Results配合使用,封装结果集的
- @One:相当于映射文件里的association,用于封装关联的一个JavaBean对象
- @Many:相当于映射文件里的collection标签,用于封装关联的一个JavaBean对象集合
3. 简单CURD操作【掌握】
查询全部用户
-
在映射器接口UserDao中增加方法
@Select("select * from user") List<User> queryAll();
-
在测试类MybatisAnnotationTest中编写测试代码
@Test public void testQueryAll(){ List<User> users = dao.queryAll(); for (User user : users) { System.out.println(user); } }
根据主键查询一个用户
-
在映射器接口UserDao中增加方法
@Select("select * from user where id = #{id}") User findById(Integer id);
-
在测试类MybatisAnnotationTest中编写测试代码
@Test public void testFindById(){ User user = dao.findById(41); System.out.println(user); }
添加用户
-
在映射器接口UserDao中增加方法
@Insert("insert into user (id,username,birthday,sex,address) values (#{id},#{username},#{birthday},#{sex},#{address})") @SelectKey( statement = "select last_insert_id()", //查询最新主键值的SQL语句 resultType = Integer.class, //得到最新主键值的类型 keyProperty = "id", //得到最新主键值,保存到哪个属性里 before = false //是否在insert操作之前查询最新主键值 ) void save(User user);
-
在测试类MybatisAnnotationTest中编写测试代码
@Test public void testSave(){ User user = new User(); user.setUsername("小红"); user.setSex("女"); user.setAddress("中粮商务公园"); user.setBirthday(new Date()); System.out.println("保存之前:" + user); dao.save(user); session.commit(); System.out.println("保存之后:" + user); }
修改用户
-
在映射器接口UserDao中增加方法
@Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}") void edit(User user);
-
在测试类MybatisAnnotationTest中编写测试代码
@Test public void testEdit(){ User user = dao.findById(57); user.setAddress("广州"); dao.edit(user); session.commit(); }
删除用户
-
在映射器接口UserDao中增加方法
@Delete("delete from user where id = #{id}") void delete(Integer id);
-
在测试类MybatisAnnotationTest中编写测试代码
@Test public void testDelete(){ dao.delete(57); session.commit(); }
JavaBean属性名和字段名不一致的情况处理
-
创建JavaBean: User2
public class User2 { private Integer userId; private String username; private Date userBirthday; private String userSex; private String userAddress; //get/set... //toString... }
-
在映射器接口UserDao中增加方法
@Select("select * from user") @Results({ @Result(property = "userId", column = "id", id = true), @Result(property = "username", column = "username"), @Result(property = "userBirthday", column = "birthday"), @Result(property = "userSex", column = "sex"), @Result(property = "userAddress", column = "address") }) List<User2> queryAllUser2();
-
在测试类MybatisAnnotationTest中编写测试代码
@Test public void testQueryAllUser2(){ List<User2> user2List = dao.queryAllUser2(); for (User2 user2 : user2List) { System.out.println(user2); } }
4. 多表关联查询
一对一(多对一)关联查询,实现懒加载
需求描述
- 需求:查询帐号信息,及其关联的用户信息
需求实现
-
修改映射器接口UserDao,增加方法findById(供关联查询时使用)
@Select("select * from user where id = #{id}") User findById(Integer id);
-
创建JavaBean:Account
注意:Account中要有User的引用,前边已经准备好
-
修改映射器接口AccountDao,增加方法
@Select("select * from account where id = #{id}") @Results({ @Result(property = "id", column = "id", id = true), @Result(property = "uid", column = "uid"), @Result(property = "money", column = "money"), @Result( property = "user", javaType = User.class, column = "uid", one = @One( //一对一关联查询,调用select配置的statement,得到关联的User对象 select = "com.guang.dao.UserDao.findById", //FetchType.LAZY 表示要使用延迟加载 fetchType = FetchType.LAZY ) ) }) Account findById(Integer id);
-
编写测试代码
@Test public void testOne2One(){ Account account = accountDao.findById(1); System.out.println(account.getId() + ", "+ account.getMoney()); //如果不执行下面这行代码,Mybatis不会发起查询用户的SQL System.out.println(account.getUser()); }
一对多(多对多)关联查询,实现懒加载
需求描述
- 需求:查询用户信息,及其关联的帐号集合信息
需求实现
-
修改映射器AccountDao,增加方法findAccountsByUid(供关联查询时使用)
@Select("select * from account where uid = #{uid}") List<Account> findAccountsByUid(Integer uid);
-
修改JavaBean:User
注意:User中需要有Account的集合,前边已经准备好
-
修改映射器接口UserDao,增加方法
/** * 查询用户信息,及其关联的帐号信息集合 * @param id * @return */ @Select("select * from user where id = #{id}") @Results({ @Result(property = "id",column = "id",id = true), @Result(property = "username",column = "username"), @Result(property = "birthday",column = "birthday"), @Result(property = "sex",column = "sex"), @Result(property = "address",column = "address"), @Result( property = "accounts", javaType = List.class, //注意,这里是List.class,而不是Account.class column = "id", many = @Many( //一对多关联查询,调用select对应的statement,得到帐号集合 select = "com.guang.dao.AccountDao.findAccountsByUid", //FetchType.LAZY 表示要使用延迟加载 fetchType = FetchType.LAZY ) ) }) User findUserAccountsById(Integer id);
-
编写测试代码
@Test public void testOne2Many(){ User user = userDao.findUserAccountsById(41); System.out.println(user.getUsername()+", " + user.getAddress()); //如果不执行下面这行代码,Mybatis不会发起查询帐号的SQL语句 System.out.println(user.getAccounts()); }
总结
- 动态sql拼接
<select>
<include refif="selectUser"/>
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" item="id" separator="," open="and id in(" close=")">
#{id}
</foreach>
</if>
<if test="user != null">
<if test="user.username != null and user.username.length() > 0">
and username like #{user.username}
</if>
</if>
</where>
</select>
<sql id="selectUser"></sql>
-
缓存
- 一级缓存:本地缓存,是SqlSession对象的缓存
- 使用SqlSession对象第一次查询一个数据,这个数据会被缓存起来
- 使用相同SqlSession,再次查询相同的数据,会优先从缓存里查找
- 当执行增删改,提交/回滚,手动清除缓存,关闭SqlSession时,会清除缓存
- 二级缓存:全局缓存,是Mapper级别的缓存,即:相同的Mapper之间可以进行数据共享
- 清除:当执行增删改时数据会清除
- 一级缓存:本地缓存,是SqlSession对象的缓存
-
多表查询
-
对一查询
- JavaBean要求:一个Account关联一个User,所以Account里要有一个User对象
- sql语句要求:查询结果集一定不能有重名列。可以给列起别名保证列名不重复
- 在映射文件里,要使用resultMap手动设置映射
<select id="xxx" resultMap="yyy"> 多表关联查询sql </select> <resultMap id="yyy" type="映射的JavaBean的全限定类名或别名"> <association property="关联JavaBean的那个属性名" javaType="关联的JavaBean的类型"> <id property="" column=""/> <result property="" column=""/> </association> </resultMap>
- 查询嵌套的方式:查询所有的帐号,及关联的用户
<select id="xxx" resultMap="yyy"> select * from account </select> <resultMap> <id property="id" column="id"/> <result property="uid" column="uid"/> .... <!-- 配置 让Mybatis调用select指定方法,并传参uid的值,查询得到User对象 --> <association property="user" javaType="User" select="com.guang.dao.UserDao.findById" column="uid"/> </resultMap>
-
对多查询
-