MyBatis保姆级理解与使用,动态SQL(核心)

1.动态SQL(核心)

1.1简介

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的难点问题。

创新互联专注为客户提供全方位的互联网综合服务,包含不限于成都网站设计、成都做网站、且末网络推广、成都小程序开发、且末网络营销、且末企业策划、且末品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联为所有大学生创业者提供且末建站搭建服务,24小时服务热线:18980820575,官方网址:www.cdcxhl.com

比如: 我们在多条件查询的时候会写这样的语句:

select * from sys_user where 1=1 and

再比如:做更新的时候,我们没有修改的数据列也执行了更新操作。

1.2 ifwhere标签

select emp_id,emp_name,emp_salary from sys_emp

or emp_name=#{empName}

2000">

or emp_salary>#{empSalary}

1.3 set标签

需求:实际开发时,对一个实体类对象进行更新。往往不是更新所有字段,而是更新一部分字段。此时页面上的表单往往不会给不修改的字段提供表单项

例如上面的表单,如果服务器端接收表单时,使用的是User这个实体类,那么userNameuserBalanceuserGrade接收到的数据就是null

如果不加判断,直接用User对象去更新数据库,在Mapper配置文件中又是每一个字段都更新,那就会把userNameuserBalanceuserGrade设置为null值,从而造成数据库表中对应数据被破坏。

此时需要我们在Mapper配置文件中,对update语句的set子句进行定制,此时就可以使用动态SQLset标签。


update sys_emp

emp_name=#{empName},

emp_salary=#{empSalary},

where emp_id=#{empId}

第一种情况:所有条件都满足 SET emp_name=?, emp_salary=?

第二种情况:部分条件满足 SET emp_salary=?

第三种情况:所有条件都不满足 update t_emp where emp_id=?

没有set子句的update语句会导致SQL语法错误

1.4trim标签

使用trim标签控制条件部分两端是否包含某些字符

prefix属性:指定要动态添加的前缀内容

suffix属性:指定要动态添加的后缀

prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值

suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值

下面例子中,trim标签内部如果有条件,则where会出现,否则where不出现

suffixOverrides=and|or”:整个条件部分,如果在后面有多出来的“andor”会被自动去掉。

select emp_id,emp_name,emp_age,emp_salary,emp_gender

from sys_emp

emp_name=#{empName} and

>3000">

emp_salary > #{empSalary} and

emp_age=#{empAge} or

emp_gender=#{empGender}

1.5choose/when/otherwise标签

在多个分支条件中,仅执行一个。

select emp_id,emp_name,emp_salary from sys_emp

where

emp_name=#{empName}

3000">emp_salary >3000

1=1

1.6 foreach标签

1.6.1 基本用法

比如:批量插入

abstract public void insertBatch(@Param(“empList”)List empList));

"insertBatch">

INSERT INTO `sys_emp`(emp_id,emp_name,emp_gender)

"empList"item="emp"separator=","open="VALUES">

(null,#{emp.empName},#{emp.empGender})

我们这里写的批量插入的例子本质上是一条SQL语句

collection属性:要遍历的集合,如果接口中方法中使用了@Param collection属性中要用这个名字。

item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象

separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符

open属性:指定整个foreach循环把字符串拼好后,这个字符串整体的前面要添加的字符串

close属性:指定整个foreach循环把字符串拼好后,这个字符串整体的后面要添加的字符串

index属性:这里起一个名字,便于后面引用

遍历List集合,这里能够得到List集合的索引值

遍历Map集合,这里能够得到Map集合的key

如果接口形参位置没有使用@Param注解,而且foreach标签也没有使用默认的名称,则会抛出异常

Causedby:org.apache.ibatis.binding.BindingException: Parameter ‘empList’ not found. Available parameters are[arg0,collection, list]

1.6.2 批量更新

而实现批量更新则需要多条update SQL语句拼起来,并且用分号分开。也就是一次性发送多条SQL语句让数据库执行

此时需要在数据库连接信息的URL地址中设置

?allowMultiQueries=true

对应的foreach标签如下:

update sys_emp set emp_name=#{emp.empName} where emp_id=#{emp.empId}

1.6.3 关于foreach标签的collection属性

如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用collectionlist来引用这个list集合。这一点可以通过异常信息看出来:

Parameter'empList'notfound. Availableparametersare[collection, list]

在实际开发中,为了避免隐晦的表达造成一定的误会,建议使用@Param注解明确声明变量的名称,然后在foreach标签的collection属性中按照@Param注解指定的名称来引用传入的参数。

1.7 sql标签

1.7.1 抽取重复的SQL片段

select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp

1.7.2 引用已抽取的SQL片段

2.缓存

2.1 缓存机制

比如:以前农村装水的水缸

2.2 一级缓存和二级缓存

二级缓存被所有的SqlSession共享,也就是能被访问的。

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

二级缓存是Mappernamespace)级别的缓存。多个SqlSession去操作同一个Mappersql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

第一次查询,先将结果放入到一级缓存,SqlSession提交事务后,再将数据放入到二级缓存。

2.2.1 一级缓存和二级缓存的使用顺序

查询的顺序是:

1先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。

2如果二级缓存没有命中,再查询一级缓存

3如果一级缓存也没有命中,则查询数据库

SqlSession关闭之前,会将一级缓存中的数据会写入二级缓存

2.2.2 效用范围

一级缓存:SqlSession级别

二级缓存:SqlSessionFactory级别

它们之间范围的大小参考下面图:

一个请求中可能包含多个事务------一个service方法(一个SqlSession)对应一个事务

注意:缓存是用再查询过程中的,增删改,反而会破坏缓存。造成缓存和数据库不一致,所以很多情况,执行了一个增删改操作,他会把缓存清空。

3. 一级缓存

Mybatis默认开启了一级缓存

3.1 案例1:测试一级缓存是否存在

为了测试缓存失效,我们需要修改测试类中的代码。需要关闭SqlSessioin对象,然后重新开,再重新开的话,需要操作SqlSessionFactory

如何测试一级缓存是否存在???同一个数据查两次,但是只发了一条SQL语句[给数据库]。

3.1.1 证明一级缓存存在

packagecom.hy.mybatis.test;

importjava.io.IOException;

importorg.apache.ibatis.io.Resources;

importorg.apache.ibatis.session.SqlSession;

importorg.apache.ibatis.session.SqlSessionFactory;

importorg.apache.ibatis.session.SqlSessionFactoryBuilder;

importorg.junit.Before;

importorg.junit.Test;

importcom.hy.bean.Emp;

importcom.hy.mapper.EmpMapper;

publicclassTestLevelOneCache1 {

privateSqlSessionFactory sqlSessionFactory;

privateLogger logger= null;

@Before

publicvoidinit() throwsIOException {

logger= LoggerFactory.getLogger(this.getClass());

sqlSessionFactory= newSqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));

}

@Test

publicvoidtestLevelOneCache1_01() throwsIOException {

SqlSession session= sqlSessionFactory.openSession();

EmpMapper empMapper= session.getMapper(EmpMapper.class);

Emp emp1= empMapper.selectById(1);

Emp emp2= empMapper.selectById(1); // 第二次是从缓存中查询出来的。

logger.info("是否相等:"+ (emp1== emp2)); //true

session.commit();

session.close();

}

}

只发一条SQL语句。一级缓存默认开启,不需要做额外的配置。二级缓存需要开启才有,现在没有开,所以只有一级缓存。

3.2 案例2一级缓存失效的情况

1同一个SqlSession两次查询期间提交了事务

2同一个SqlSession两次查询期间执行了任何一次增删改操作,不论是够提交事务,都清空一级缓存

3同一个SqlSession但是查询条件发生了变化

4同一个SqlSession两次查询期间手动清空了缓存

5不是同一个SqlSession

总结:在执行commitrollback时会清空一级缓存

一级缓存的清除还有以下两个地方:

1、就是获取缓存之前会先进行判断用户是否配置了flushCache=true属性(参考一级缓存的创建代码截图),如果配置了则会清除一级缓存。

2MyBatis全局配置属性localCacheScope配置为Statement时,那么完成一次查询就会清除缓存。

3.2.1 同一个SqlSession两次查询期间提交了事务

publicclassTestLevelCache1 {

privateSqlSessionFactory sqlSessionFactory;

privateLogger logger= null;

@Before

publicvoidinit() throwsIOException {

logger= LoggerFactory.getLogger(this.getClass());

sqlSessionFactory= newSqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));

}

//1) 同一个SqlSession两次查询期间提交了事务

//1.1 没有提交事务,使用一级缓存

@Test

publicvoidtestLevelOneCache1_01() throwsIOException {

SqlSession session= sqlSessionFactory.openSession();

EmpMapper empMapper= session.getMapper(EmpMapper.class);

Emp emp1= empMapper.selectById(1);

Emp emp2= empMapper.selectById(1); // 第二次是从缓存中查询出来的。

logger.info("是否相等:"+ (emp1== emp2)); //true

session.commit();

session.close();

}

//1.2 两次查询中间,提交了事务,清空一级缓存

@Test

publicvoidtestLevelOneCache1_02() throwsIOException {

SqlSession session= sqlSessionFactory.openSession();

EmpMapper empMapper= session.getMapper(EmpMapper.class);

Emp emp1= empMapper.selectById(1);

session.commit();

Emp emp2= empMapper.selectById(1); // 由于进行了事务的提交,所以又发送了一条查询语句。

logger.info("是否相等:"+ (emp1== emp2)); //false

session.close();

}

}

3.2.2 同一个SqlSession两次查询期间执行了任何一次增删改操作,并且提交了事务

@Test

publicvoidtestLevelOneCache01_4() {

SqlSession sqlSession= sqlSessionFactory.openSession();

EmpMapper empMapper= sqlSession.getMapper(EmpMapper.class);

Emp emp1= empMapper.selectById(1);

//EmpnewEmp = new Emp(2L, "范冰冰ILoveYou", null, null, null, null);

//empMapper.updateById(newEmp); //更新但是不提交事务

empMapper.deleteById(8); //执行了删除没有,提交事务也,重新一遍

Emp emp2= empMapper.selectById(1);

//logger.debug(emp2.toString());

logger.debug("是否相等:"+ (emp1== emp2));

sqlSession.commit();

sqlSession.close();

}

// 2.2

@Test

publicvoidtestLevelOneCache2_02() throwsIOException {

SqlSession session= sqlSessionFactory.openSession();

EmpMapper empMapper= session.getMapper(EmpMapper.class);

Emp emp1= empMapper.selectById(1); //查询后,将对象放入一级缓存

empMapper.deleteById(7); //执行了delete操作,但是并没有提交

session.commit();

Emp emp11= empMapper.selectById(1); //提交事务,一级缓存清空

logger.info("是否相等:"+ (emp1== emp11)); //false

session.commit();

session.close();

}

第一次发起查询用户id1的用户信息,先去找缓存中是否有id1的用户信息,如果没有,从数据库查询用户信息,将查询到的用户信息存储到一级缓存中。

如果中间sqlSession去执行插入、更新、删除,并且commit,清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户id1的用户信息,先去找缓存中是否有id1的用户信息,缓存中有,直接从缓存中获取用户信息。如果没有,则重新发出一条查询语句。

3.2.3 同一个SqlSession但是查询条件发生了变化

// 3. 同一个SqlSession但是查询条件发生了变化

@Test

publicvoidtestLevelOneCache3_01() throwsIOException {

SqlSession session= sqlSessionFactory.openSession();

EmpMapper empMapper= session.getMapper(EmpMapper.class);

Emp emp1= empMapper.selectById(1); //查询后,将对象放入一级缓存

//查询的是emp_id2的员工信息,一级缓存中并没有所以重新发出一条SQL语句

Emp emp2= empMapper.selectById(2);

session.commit();

session.close();

}

3.2.4 同一个SqlSession两次查询期间手动清空了缓存

// 4)同一个SqlSession两次查询期间手动清空了缓存

@Test

publicvoidtestLevelOneCache4() throwsIOException {

SqlSession session= sqlSessionFactory.openSession();

EmpMapper empMapper= session.getMapper(EmpMapper.class);

Emp emp1= empMapper.selectById(1);

session.clearCache();

Emp emp2= empMapper.selectById(1); // 由于两次查询中间清空了一级缓存,所以又发送了一条查询语句。

logger.info("是否相等:"+ (emp1== emp2)); //false

session.commit();

session.close();

}

3.2.5 不是同一个SqlSession

// 5)不是同一个SqlSession

@Test

publicvoidtestLevelOneCache1_05() throwsIOException {

SqlSession session01= sqlSessionFactory.openSession();

SqlSession session02= sqlSessionFactory.openSession();

EmpMapper empMapper01= session01.getMapper(EmpMapper.class);

EmpMapper empMapper02= session02.getMapper(EmpMapper.class);

Emp emp1= empMapper01.selectById(1);

// 由于是不同的SqlSession,所以又发送了一条查询语句。各自有各自的一级缓存。

Emp emp2= empMapper02.selectById(1);

logger.info("是否相等:"+ (emp1== emp2)); //false

session01.commit();

session02.commit();

session01.close();

session02.close();

}

4. 二级缓存[mybatis自带的二级缓存]

4.1 使用二级缓存步骤

1在想要使用二级缓存的EmpMapper映射文件中加入cache标签

开启全局二级缓存配置:

2让实体类支持序列化

publicclassEmp implementsSerializable{

privatestaticfinallongserialVersionUID= 1L;

这里我们使用mybaits自带的二级缓存,后面我们使用第三方的二级缓存EHCache

3SqlSession提交事务时才会将查询到的数据存入二级缓存

@Test

publicvoidtestLevelTwoCache() {

// 测试二级缓存存在:使用两个不同SqlSession执行查询

//说明:SqlSession提交事务时才会将查询到的数据存入二级缓存

// 所以本例并没有能够成功从二级缓存获取到数据

SqlSessionsqlSession01=sqlSessionFactory.openSession();

SqlSessionsqlSession02=sqlSessionFactory.openSession();

EmpMapperempMapper01=sqlSession01.getMapper(EmpMapper.class);

EmpMapperempMapper02=sqlSession02.getMapper(EmpMapper.class);

//[00:01:07.699] [DEBUG] [main] [com.hy.mapper.EmpMapper] [Cache Hit Ratio [com.hy.mapper.EmpMapper]: 0.0]

Empemp01=empMapper01.selectById(1);

//[00:01:07.746] [DEBUG] [main] [com.hy.mapper.EmpMapper] [Cache Hit Ratio [com.hy.mapper.EmpMapper]: 0.0]

Empemp02=empMapper02.selectById(1);

logger.info("是否相等:"+ (emp1== emp2)); //false

sqlSession01.commit();

sqlSession01.close();

sqlSession02.commit();

sqlSession02.close();

}

修改代码;

@Test

publicvoidtestSecondLevelCache2() {

SqlSessionsqlSession01=sqlSessionFactory.openSession();

SqlSessionsqlSession02=sqlSessionFactory.openSession();

EmpMapperempMapper01=sqlSession01.getMapper(EmpMapper.class);

EmpMapperempMapper02=sqlSession02.getMapper(EmpMapper.class);

Empemp01=empMapper01.selectById(1);

sqlSession01.commit();

sqlSession01.close();

Empemp02=empMapper02.selectById(1);

//false注意,从二级缓存中new了一个新的对象,所以emp01emp02不是同一个对象

logger.info("是否相等:"+ (emp1== emp2));

sqlSession02.commit();

sqlSession02.close();

}

虽然两个对象不相等,但是注意:只发出了一条查询语句。

日志中打印的Cache Hit Ratio叫做缓存命中率

缓存命中率=命中缓存的次数/查询的总次数

4mybatis序列化的特点

mybatis的二级缓存是属于序列化,序列化的意思就是从内存中的数据传到硬盘中,这个过程就是序列化;

反序列化意思就是相反而已;

也就是说,mybatis的二级缓存,实际上就是将数据放进了硬盘文件中去了;

现在呢,你仅仅的将Emp类给序列化了,如果有父类Person、级联属性,它们是不会跟着被序列化的,所以光这些是不够的;

很简单,如果Emp需要序列化,但是这个类中还有其他类的属性,仅需将其他类也实现序列化接口即可!

比如Emp类继承了Person父类,那么父类也需要实现Serializable这个接口;

4.2 二级缓存失效的条件

与一级缓存一样,二级缓存也会存在失效的条件的,下面我们就来探究一下哪些情况会造成二级缓存失效。

4.2.1 第一次SqlSession未提交

SqlSession在未提交的时候,SQL语句产生的查询结果还没有放入二级缓存中,这个时候SqlSession2在查询的时候是感受不到二级缓存的存在的

4.2.2 更新对二级缓存影响

与一级缓存一样,更新操作对二级缓存造成影响,下面用三个 SqlSession来进行模拟,第一个SqlSession只是单纯的查询并提交,第二个SqlSession用于查询二级缓存是否失效,第三个SqlSession2用于执行更新操作。

@Test

publicvoidtestLevelTwoCache01_03() {

SqlSession sqlSession1= sqlSessionFactory.openSession();

SqlSession sqlSession2= sqlSessionFactory.openSession();

SqlSession sqlSession3= sqlSessionFactory.openSession();

EmpMapper empMapper1= sqlSession1.getMapper(EmpMapper.class);

EmpMapper empMapper2= sqlSession2.getMapper(EmpMapper.class);

EmpMapper empMapper3= sqlSession3.getMapper(EmpMapper.class);

Emp emp1= empMapper1.selectById(1);

logger.info(emp1.toString());

sqlSession1.commit(); //清空一级缓存,加入二级缓存

Emp emp2= empMapper2.selectById(1);

logger.info(emp2.toString()); //从二级缓存中查询出来

logger.info("是否是同一个对象:"+(emp1==emp2));

sqlSession2.commit();

Emp newEmp= newEmp(1L,"垃圾张文宏",null,null,null,null);

empMapper3.updateById(newEmp);

sqlSession3.commit();

Emp emp22= empMapper2.selectById(1);

logger.info(emp22.toString()); //从二级缓存中查询出来

logger.info("是否是同一个对象:"+(emp1==emp22));

sqlSession2.commit();

sqlSession1.close();

sqlSession2.close();

}

因为所有的insert,delete,uptede都会触发缓存的刷新,从而导致二级缓存失效,所以二级缓存适合在读多写少的场景中开启。


文章标题:MyBatis保姆级理解与使用,动态SQL(核心)
链接URL:http://csdahua.cn/article/dscgeho.html
扫二维码与项目经理沟通

我们在微信上24小时期待你的声音

解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流