1. Mybatis的连接技术
1. Mybatis连接池的分类
UNPOOLED 不使用连接池的数据源
POOLED 使用连接池的数据源
JNDI 使用JNDI实现的数据源(了解)
在MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的UnpooledDataSource,PooledDataSource类来表示UNPOOLED、POOLED类型的数据源。
2. Mybatis中数据源的配置
我们的数据源配置就是在 SqlMapConfig.xml 文件中,具体配置如下
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
MyBatis 在初始化时,根据<dataSource>
标签中的type属性来创建相应类型的的数据源 DataSource
- type=”POOLED”:MyBatis 会创建 PooledDataSource 实例
- type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例
- type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用
3. Mybatis的事务控制
手动提交事务
/ 提交事务
session.commit();
/ 回滚事务
session.rollback();
自动提交事务
一般来说,都需要手动提交事务,但是在创建session对象时,我们可以设置提交方式,但是这种方式不常用
//4.创建 SqlSession 对象 设置自动提交事务
session = factory.openSession(true);
2. Mybatis的动态SQL语句
1. 动态标签之<if>
标签
如果传递的sex参数不为空,就继续查下去
关键在于持久层映射配置文件的配置
<select id="conditionFind" resultType="com.love.code.domain.User" parameterType="com.love.code.domain.User">
select * from user01 where 1=1
<if test="username!=null"> <!--这个username是paramterType输入配置sql语句参数 -->
and username=#{username}<!--第一个username是指数据库中的列名,第一个username是指输入配置sql语句参数-->
</if>
<!--上面这一段话相当于 满足username!=null的话 select * from user01 where 1=1 and username=? 当然还可以继续加条件 -->
<if test="sex!=null">
and sex=#{sex}
</if>
</select>
上面这一段sql语句的含义:查询 显示所有 从user01表中查询,满足条件 where 1=1 (如果传递的username参数不为空) andusername=? (如果传递的sex参数不为空) and sex=? 这么一个sql语句
2. 动态标签之<where>
标签
为了简化上面 where 1=1 的条件拼装,我们可以采用标签来简化开发。
<select id="conditionFind" resultType="com.love.code.domain.User" parameterType="com.love.code.domain.User">
select * from user01 <where>
<if test="username!=null">
and username=#{username}
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
</where>
</select>
这里的where标签代替的 where1=1
3. 动态标签之<foreach>
标签
当我们想要执行如下sql语句时
SELECT * FROM user01 WHERE id IN(41,42,43,46,57)
就要用到foreach标签
实体类对象
public class QueryVo {
private User user;
private List<Integer> ids;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
xml映射配置
<select id="findInIds" resultType="com.love.code.domain.User" parameterType="com.love.code.domain.QueryVo">
select * from user01
<where>
<if test="ids!=null ">
<foreach collection="ids" open=" id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
那么上面这一段是上面意思呢?
collection:代表实体对象要遍历集合的属性,这里的集合是 private List< Integer > ids
item:代表遍历集合的每个元素,生成的变量名,必须和下面的#{id}对应
sperator:代表分隔符
open:代表语句的开始部分
close:代表结束部分
@Test
public void testFindInIds() {
QueryVo vo = new QueryVo();
List<Integer> ids = new ArrayList<Integer>();
ids.add(41);
ids.add(42);
ids.add(43);
ids.add(46);
ids.add(57);
vo.setIds(ids);
//6.执行操作
List<User> users = userDao.findInIds(vo);
for(User user : users) {
System.out.println(user);
}
}
3. Mybatis多表查询
数据库表关系
如上表
我们可以从左边查询到右边(User表——>Account表) 一对多
我们可以从右边查询到左边(Account表——>User表) 一对一
1.Account表主查询( 一对一)
要求:查询所有账户信息,关联查询下单用户信息
实现方式一
创建一个实体对象,让该对象继承Account实体对象,然后加上我们想要的User表中的属性
上面这种操作相当于,重新定义一个实体对象,里面有account表中的全属性和想要的user表中的部分属性属性
sql语句
SELECT account.*,user01.username,user01.address FROM account,user01 WHERE account.uid = user01.id
查询 显示account表的全部和user01表的username,address从account表已经user01表中查询,并且条件为 user01的id等于account的uid
实现方式二
定义account实体对象时,把user对象包含进来
关键在于,返回的结果集该怎么一一对应呢?因为返回的是user表中的属性,而account01实体对象中并没有这些属性,只有user属性中的属性才能对应
解决办法:resultMap标签配置
使用resultMap标签中association标签,往下在深入一层即可
resultMap配置代码
<resultMap id="Accounts" type="com.it.domain.Accounts">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user" column="user" javaType="com.it.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
</association>
</resultMap>
<!-- 执行的SQL语句-->
<select id="findAccounts" resultMap="Accounts">
SELECT account.*, user.*
FROM account,user
WHERE account.uid = user.id
</select>
2.User表主查询(一对多)
首先分析,那一方是多,一个user用户下有多个账户,所以user为一,account为多
一个user用户中,可以包含多个用户
<mapper namespace="it.cast.dao.IUserDao">
“resultMap映射配置”
<resultMap id="user01" type="it.cast.domain.User01">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<!-- 配置集合中的属性-->
<collection property="accs" ofType="it.cast.domain.Account" >
<id property="id" column="aid" ></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
sql语句
<select id="findAll" resultMap="user01" >
select u.*,a.id as aid ,a.uid,a.money from user01 u left outer join account
a on u.id =a.uid
</select>
</mapper>
注意:type类型和ofType类型配置时使用全限定类名
如果在sqlMapConfig.xml配置了的话可以不用全限定类名称
附注AccountSQL语句表
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `account`(`ID`,`UID`,`MONEY`) values (1,41,1000),(2,45,1000),(3,41,2000);
4. Mybatis多表查询之一对多
1.实现Role到User多对多
如下图所示
数据库分析图
怎么样才能将查询结果,封装到Java结果集中去呢?
1.创建视图实体Role对象
2.配置映射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="it.cast.dao.IRoleDao">
<resultMap id="Role" type="it.cast.domain.Role">
<id property="roleId" column="Id" ></id>
<result property="roleName" column="role_Name" ></result>
<result property="roleDesc" column="role_Desc" ></result>
<collection property="users" ofType="it.cast.domain.User">
<id property="id" column="uid"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<result property="address" column="address"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="Role">
SELECT r.*,u.id uid, u.username username, u.birthday birthday, u.sex sex, u.address address
FROM ROLE r INNER JOIN USER_ROLE ur ON ( r.id = ur.rid) INNER JOIN USER01 u ON (ur.uid = u.id);
</select>
</mapper>
3.测试方法
@Test
public void testFindAll(){
List<Role> list = iRoleDao.findAll();
for (Role role : list) {
System.out.println(role);
}
}
练习:实现User到Role的多对多(关键在于sql语句和xml配置)
附注:SQL语句
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`ID` int(11) NOT NULL COMMENT '编号',
`ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
`ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`,`RID`),
KEY `FK_Reference_10` (`RID`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);
4. Mybatis的延迟查询
查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载
1. 实现原理
上面可以看到是一对一实现延迟加载是使用的resultMap中的association标签
其实实现一对多延迟加载也是使用的resultMap中的collection标签
2. 开启Mybatis的延迟加载策略
SqlMapConfig.xml文件,配置如下代码
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3. 实现一对一延迟加载
1. 定义实体对象
public class Account {
private Integer id;
private Integer uid;
private Double money;
//定义user对象属性
private User user;
2 添加持久层方法
/**
* 查询账户(Account)信息并且关联查询用户(User)信息。
* 如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。
* 把对用户(User)信息的按需去查询就是延迟加载。
* @return
*/
List<Account> findAll();
3 配置持久层映射文件
<resultMap type="it.cast.domain.Account" id="accountMap">
<id column="id" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 -->
<association property="user" javaType="it.cast.domain.User"
如果需要,就调用这个方法
select="it.cast.dao.IUserDao.findById"
调用上面这个方法传递属性
column="uid">
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
原理图
4.开启Mybatis的延迟加载策略
在sqlMapConfig.xml文件中添加如下开启设置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
注意在< configuration >标签下
5.测试代码
@Test
public void testFindAll(){
List<Account> accounts = iAccountDao.findAll();
for (Account as : accounts) {
System.out.println(as);
}
}
6.结果分析
如上图,查询代码都执行了,这是因为在测试代码中,我们调用了遍历方法,所以使用了第二个查询方法,要是我们不遍历,不使用的结果如下
4. 实现一对多延迟加载
持久层接口映射文件(IUserDao.xml)
<resultMap type="user" id="userMap"> <id column="id" property="id"></id> <result column="username" property="username"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<!-- collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
column 是用于指定使用哪个字段的值作为条件查询
--> <collection property="accounts" ofType="account"
select="com.itheima.dao.IAccountDao.findByUid"
column="id">
</collection>
</resultMap>
其他的参照一对一实现延迟加载和一对多的多表查询
5.Mybatis缓存
1.Mybatis一级缓存
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。
通过id查询用户:通过id查询同一个用户两次,看看sql语句执行了几次?
@Test
public void testFindById(){
User u1 = iUserDao.findById(41);
System.out.println("第一次查询" +u1);
User u2 = iUserDao.findById(41);
System.out.println("第二次查询" +u2);
System.out.println(u1==u2);
}
查询结果
我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是 Mybatis 提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询 id 为 41 的记录时,并没有发出 sql 语句从数据库中查询数据,而是从一级缓存中查询。
1.原理分析
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存
第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
2.测试一级缓存的清空
@Test
public void testFindById(){
SqlSession session1 = factory.openSession();
IUserDao iUserDao1 = session1.getMapper(IUserDao.class);
User u1 = iUserDao1.findById(41);
System.out.println("第一次查询" +u1);
session1.close(); / 关闭一下session对象
System.out.println("*****************************");
SqlSession session2 = factory.openSession();
IUserDao iUserDao2 = session2.getMapper(IUserDao.class);
User u2 = iUserDao2.findById(41);
System.out.println("第二次查询" +u2);
System.out.println(u1==u2);
session2.close();
}
结果分析
当执行sqlSession.close()后,再次获取sqlSession并查询id=41的User对象时,又重新执行了sql语句,从数据库进行了查询操作。
2.Mybatis二级缓存
1.原理分析
二级缓存是mapper映射级别的缓存,多个SqlSession 去操作同一个 Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
原图图如下
2.二级缓存的设置
开启
第一步:在 SqlMapConfig.xml 文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持 ->
<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为false 代表不开启二级缓存。
第二步:配置相关的 Mapper 映射文件(持久层的xml文件)
第三步:配置statement上面的useCache属性
第四步:二级缓存测试
public class SecondLevelCacheTest {
private InputStream in;
private SqlSessionFactory factory;
@Before//用于在测试方法执行之前执行
public void init() throws Exception {
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取 SqlSessionFactory
factory = new SqlSessionFactoryBuilder().build(in);
}
@After//用于在测试方法执行之后执行
public void destroy() throws Exception {
in.close();
}
/**
* 测试一级缓存
*/
@Test
public void testFirstLevelCache() {
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);
}
}
结果分析
经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出 sql 语句,所以此时的数据就只能是来自于我们所说的二级缓存。
当我们在使用二级缓存时,所缓存的类一定要实现java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
从上图我们可以发现,两个IUserDao对象不一样,为什么呢?
因为:由原理图可以知道,二级缓存存的数据不是对象,SqlSessionFactory工厂把数据给不同session对象,所以session对象当然不一样!
6. 使用Mybatis注解实现基本CRUD
1.查询方法
查询所有
持久层接口方法
/**
* 查询所有
*/
@Select(value ={"select * from user01"})
List<User> findAll();
通过id查询
/**
* 通过id查询
*/
@Select(value = {"select * from user01 where id=#{id}"})
User findById(Integer integer);
查询使用聚合函数
/**
* 查询使用聚合函数
* @return
*/
@Select("select count(*) from user01 ")
int findTotal();
模糊查询
/**
* 模糊查询
* @param name
* @return
*/
@Select("select * from user01 where username like #{username} ")
List<User> findByName(String name);
2.添加方法
添加用户
/**
* 添加一个用户
*/
@Insert(value = {"insert into user01(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})"})
int AddUser(User user);
3.更新方法
更新用户
/**
* 更新用户操作
*
*/
@Update("update user01 set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id =#{id}")
/**如果只有一个值的话,可以不使用value={“”}*/
int updateUser(User user);
4.删除方法
删除用户
/**
* 删除用户
*/
@Delete("delete from user01 where id = #{uid} ")
int deleteUserById(Integer id);
7. 使用Mybatis注解实现复杂CRUD
先来看一下mybatis注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
1.使用注解实现一对一复杂关系映射及延迟加载
需求:
加载账户信息时并且加载该账户的用户信息,根据情况可实现延迟加载。(注解方式实现)
查询结果对应的实体Account
注解方式
因为是一对一,所以用@One
/**
* 查询所有账户,采用延迟加载的方式查询账户的所属用户
* @return
*/
@Select(value = {"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(column="uid", property="user",
one=@One(select="it.cast.dao.IUserDao.findById", fetchType=FetchType.EAGER) )
})
List<Account> findAll();
注解配置详解析
有主键:对应id 设置为true
2.使用注解实现一对多复杂关系映射
需求:
查询用户信息时,也要查询他的账户列表。使用注解方式实现。
分析:
一个用户具有多个账户信息,所以形成了用户(User)与账户(Account)之间的一对多关系
查询结果对应的实体User01
因为是一对一,所以用@Many
@Select(value = {"select * from user01"})
@Results(
value = {
@Result(id=true,column="id",property="id"),
@Result(column="username",property="username"),
@Result(column="sex",property="sex"),
@Result(column="address",property="address"),
@Result(column="birthday",property="birthday"),
@Result(column="id",property="accounts",
many=@Many(select="it.cast.dao.IAccountDao.findById", fetchType=FetchType.LAZY)) })
List<User01> findAllUser();
注解配置详解析
3.mybatis基于注解的二级缓存
在mybatis注解中,一级缓存是自动就有的不用去设置什么
但是二级缓存需要配置
在 SqlMapConfig 中开启二级缓存支持
<!-- 配置二级缓存 --> <settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/></settings>
在持久层接口中使用注解配置二级缓存
测试代码
@Test
public void testFindAll() {
session = factory.openSession();
iAccountDao = session.getMapper(IAccountDao.class);
Account u1 = iAccountDao.findById(45);
session.close(); // 关闭一级缓存
SqlSession session2 = factory.openSession();
IAccountDao iAccountDao2 = session2.getMapper(IAccountDao.class);
Account u2 = iAccountDao2.findById(45);
session2.close();
}
没有开启二级缓存:看一下执行了几次sql语句
有两条sql语句
开启二级缓存:看一下执行了几次sql语句
很明显,只有一条sql语句