动态SQL
Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。
(比如多条件查询的设置,实现判空等)
1、if
创建DynamicSQLMapper接口,DynamicSQLMapper.xml文件
DynamicSQLMapper接口: List
getEmpByCondition(Emp emp); DynamicSQLMapper.xml中: List集合作为方法的返回值,以防查出多个数据。
以上用if条件作为sql的查询判断筛选结果,但存在问题,原意是判断如果查询条件中姓名不为空(即需要查询姓名)则将姓名条件赋值,后面对年龄、性别的if语句也是做同样的判断,实现最终sql语句的拼接。
创建测试方法DynamicSQLMapperTest
public void testGetEmpByCondition(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); Emp emp = new Emp(null, "大大", 7, "女"); List
empByCondition = mapper.getEmpByCondition(emp); empByCondition.forEach(System.out::println); } 从赋值条件可以看出,是实现查询姓名为大大、年龄为7、性别为女的记录。如下得到对应的查询结果,我们可以知道如果只有性别做了指定,到那时其余为空,则我们将查询到男或女的所有数据。(这个实现其实就是if的逻辑,如果我们设定的条件不为空则会拼接到sql语句,进行条件查询)
但是如果第一个条件(上述代码为对empName是否赋值的判断)不成立,where后则会直接凭借下一个语句,为符合和语法(where后面不可以直接加and),一般会在where语句后加上一个恒成立条件,拼接较为完善的sql语句
2、where
where和if一般结合使用:
a>若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字eg:上述条件都不满足,则会执行select * from t_emp
b>若where标签中的if条件满足,则where标签会自动添加where关键字,eg:第一个条件满足,但前面没有where会自动添加即select * from t_emp where emp_name = #{empName}
并将条件最前方多余的and去掉 ,eg:第一个条件不满足,后面带有and的条件满足,则自动去掉and拼接,
select * from t_emp where age = #{age} and gender = #{gender} (后面两个都满足)
注意:where标签不能去掉条件最后多余的and(即把and放在条件后面时,age = #{age} and)3、trim
trim用于去掉或添加标签中的内容
常用属性:
prefix:在trim标签中的内容的前面添加某些内容
prefixOverrides:在trim标签中的内容的前面去掉某些内容
suffix:在trim标签中的内容的后面添加某些内容
suffixOverrides:在trim标签中的内容的后面去掉某些内容4、choose、when、otherwise
choose、when(if else...if)、otherwise(else)相当于if...else if..else
(when至少设置一个,otherwise最多设置一个)
上述逻辑:当为empName赋值查询(即不为null或空字符串时)将emp_name = #{empName}拼接到sql语句,但是后面的内容将不再判断执行,只要有一个满足要求,后面都不会再继续判断,而当前面都不满足代码中有othwise标签,则执行该标签的内容(即都没有给empName合age赋值,则根据gender赋值查询(注意,当查询内容条件都为空时,因为在where标签下,会自动补上where,则查询语句变成select * from t_emp WHERE gender = ?,查询结果如下
a
5、foreach
1、用foreach实现批量添加
insert into t_emp values (null,#{emp.empName},#{emp.age},#{emp.gender},null) 如果传输过来的是list集合数据,collection属性应该写list,可以采取@param注解方法,那么下次直接填写设置的参数名即可。即@param(“emps”),则collection=emps获取该list集合数据。
且item的赋值就是个参数名,可以自己取,记得插入的值不是empName,而是emp.empName(因为item在这里为emp,所以是item.empName),我们获取的是list集合,里面都是由Emp对象组成,所以要获取单个属性值,应该是对象.属性名的形式(我是这么理解的)
上面代码的相关属性如下,可以知道separator=”,“是为了实现插入多个数值并隔开的效果,values(),(),(),()(不是单纯的在插入语句后加,因为最后还有会一个逗号存在
属性: collection:设置要循环的数组或集合 item:表示集合或数组中的每一个数据 separator:设置循环体之间的分隔符 open:设置foreach标签中的内容的开始符 close:设置foreach标签中的内容的结束符
测试代码如下,
public void testinsertMoreEmp(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); Emp emp = new Emp(null, "小饿", 3, "男"); Emp emp1 = new Emp(null, "紫紫", 7, "女"); Emp emp2 = new Emp(null, "凹凸", 4, "女"); List
emps = Arrays.asList(emp,emp1,emp2); mapper.insertMoreEmp(emps); } 插入结果如图
(补充:mapper接口的方法中参数值为list的时候,mybatis都会把数据放在map中,以list为键,当前参数为值,如果是数组,则以array为键,当前参数为值
eg
如下为数组形式的测试方法:
public void testinsertMoreEmpArray(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); Emp emp = new Emp(null, "小粉", 3, "女"); Emp emp1 = new Emp(null, "小黑", 4, "女"); Emp emp2 = new Emp(null, "三日兔", 7, "女"); Emp[] emps = new Emp[]{emp,emp1,emp2}; mapper.insertMoreEmpArray(emps); }
所以还是用注解好,直接写注解取的名字。
2、用foreach实现批量删除
delete from t_emp where emp_id in ( #{empId} )其括号可以如下替换表示
或是
(seperator=or,其实在拼接sql语句时or前后自动由空格)
如下为测试方法,删除id为10和13的记录
public void testdeleteMoreEmp(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); Integer[] empIds = {10, 13}; mapper.deleteMoreEmp(empIds); }
SQL片段
sql标签,将常用的sql片段进行记录,用include的标签在需要时引入。
emp_id,emp_name,age,gender,dept_id id名自取
MyBatis的缓存
1、MyBatis的一级缓存(默认开启)
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况: 1) 不同的SqlSession对应不同的一级缓存 (所以如果是如下测试创建并使用了不同的SqlSession, public void testGetEmpById(){ SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp empById = mapper1.getEmpById(1); System.out.println(empById); Emp empById1 = mapper1.getEmpById(1); System.out.println(empById1); SqlSession sqlSession2 = SqlSessionUtil.getSqlSession(); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); Emp empById2 = mapper2.getEmpById(1); System.out.println(empById2); } 则会执行两次sql语句,第一次打印两个结果(对应empById、empById1,结果缓存,直接读取),第二次就是后面再次创建调用SqlSession,再次执行了sql语句 2) 同一个SqlSession但是查询条件不同 3) 同一个SqlSession两次查询期间执行了任何一次增删改操作 (因为任意一次增删改会清除缓存,查询数据更新) 4) 同一个SqlSession两次查询期间手动清空了缓存 sqlSession1.clearCache();
2、MyBatis的二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
(二级缓存需要手动设置,一级不用)
二级缓存开启的条件:
a>在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
b>在映射文件中设置标签
c>二级缓存必须在SqlSession关闭或提交之后有效sqlSession.close()
d>查询的数据所转换的实体类类型必须实现序列化的接口public void testCache() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); CacheMapper mapper = sqlSession.getMapper(CacheMapper.class); Emp empById = mapper.getEmpById(1); System.out.println(empById); sqlSession.close();//数据默认保存到一级缓存,只要关闭或提交sqlSession后才会将存入二级缓存 SqlSession sqlSession1 = sqlSessionFactory.openSession(true); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp empById1 = mapper1.getEmpById(1); System.out.println(empById1); } }
上述代码没有用工具类,因为要确保。通过同一个SqlSessionFactory创建的SqlSession查询
使二级缓存失效的情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
3、二级缓存的相关配置在mapper配置文件中添加的cache标签可以设置一些属性: eviction属性:缓存回收策略 LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。 FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 默认的是 LRU。 flushInterval属性:刷新间隔,单位毫秒 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新 size属性:引用数目,正整数 代表缓存最多可以存储多少个对象,太大容易导致内存溢出 readOnly属性:只读,true/false true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。 false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
4、MyBatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。 如果二级缓存没有命中,再查询一级缓存 如果一级缓存也没有命中,则查询数据库 SqlSession关闭之后,一级缓存中的数据会写入二级缓存
5、整合第三方缓存EHCache(针对二级缓存,整合第三方缓存实现二级缓存
Ehcache 是一个流行的开源 Java 缓存框架,用于将数据存储在内存中,以提高应用程序的性能和响应速度。MyBatis 提供了对 Ehcache 的集成支持,可以将 Ehcache 作为 MyBatis 的二级缓存实现。
a>添加依赖
org.mybatis.caches mybatis-ehcache 1.2.1 ch.qos.logback logback-classic 1.2.3 b>各jar包功能
c>在resources下创建EHCache的配置文件ehcache.xml
上面的路径是指将日志等存储到我们电脑的路径,如下是最后运行所生成的文件及其存放路径
配置文件说明
d>设置二级缓存的类型
在 MyBatis 的 XML 配置文件中,使用
标签,通过设置
type
属性为org.mybatis.caches.ehcache.EhcacheCache
,可以启用 Ehcache 缓存作为二级缓存。这样配置后,MyBatis 将使用 Ehcache 来缓存查询结果,减少与数据库的交互次数,提高系统的性能。在CacheMapper.xml文件中:
e>加入logback日志
存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。
在resources下创建logback的配置文件logback.xml
[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n (这部分只要了解可以第三方配置即可)
MyBatis的逆向工程
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成Java实体类、Mapper接口
、Mapper映射文件1、创建逆向工程的步骤
a>添加依赖和插件 (在pom.xml文件中添加)
org.mybatis mybatis 3.5.7 org.mybatis.generator mybatis-generator-maven-plugin 1.3.0 org.mybatis.generator mybatis-generator-core 1.3.2 com.mchange c3p0 0.9.2 mysql mysql-connector-java 5.1.8 b>创建MyBatis的核心配置文件
c>创建逆向工程的配置文件(文件名必须是:generatorConfig.xml
(注意是userId不是username)
我们知道到这一步后,文件目录如下
在配置完成后点击下图阴影部分
再看文件目录可以发现,生成很多之前我们需要手动创建的文件
(注意,
1、上述自动构建的过程,只是满足我们单表操作的需求。记得utils包下的工具类等要自己取创建,properitise文件等。(只是生成基本的数据表对应的实体类,接口和配置文件等)
2、如果想要再次生成已经生成的文件,记得把原来的文件删除,避免在原有基础上追加出现问题。
3、在接口中生成的基本的增删改查,且生成的实体类只有简单的getter和setter方法,没有toString,要自己手动添加。(属性名有一定规律,符合字段的驼峰形式,例如emp_id
字段对应的属性名empId)
4、生成后我们在自己完善mybatis-config.xml内容。
5、MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)(CRUD是指在做计算处理时的增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写。主要被用在描述软件系统中DataBase或者持久层的基本操作功能。)
下面将会生成Mybatis3
(如果是在之前生成了的基础上再去进行如下操作,记得删掉之前自动生成的文件)
在generatorConfig.xml中修改targetRuntime
再执行
结果如图
(和前面的区别主要就是生成的接口方法更多更全面
(记得如果要用@Test注解,使用测试单元,要在配置文件添加junit依赖
junit junit 4.12 发现如下问题,
答案是:pom.xml文件中要把MySQL驱动配置放在
,而且我也修改了这个版本,应该和版本没关系。 标签外面 创建测试文件去使用自动生成的方法,发现还挺好用。
我们要根据方法名知道其用法,上述代码中selectByPrimaryKey是根据主键去查询,
empExample.createCriteria().andEmpNameEqualTo("紫紫").andAgeGreaterThan(3); empExample.or().andGenderEqualTo("女");也是根据其意思知道,逻辑是匹配名字为紫紫且年龄大于3的数据,还有将性别为女的记录全部输出。创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系,将之前添加的条件通过or拼接其他条件。
public void testMBG(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); EmpExample empExample = new EmpExample(); empExample.createCriteria().andEmpNameEqualTo("大大"); List
emps = mapper.selectByExample(empExample); emps.forEach(System.out::println); Emp emp1 = new Emp(null,"小紫",null,"女",3); int i = mapper.updateByExampleSelective(emp1, empExample); System.out.println(i); } 上面对比结果可以知道,一些方法的用法,尤其是selective的方法,和之前的清晰简洁版不一样的是,会对一些条件进行判断,如上面使用的方法(updateByExampleSelective(emp1, empExample);)的意思就是对比empExample,如果emp1中有条件为null(即未被赋值条件)时不会直接用null覆盖原来的值,而简洁版生成的方法,会将null直接覆盖。
分页插件
1、分页插件使用步骤
先了解一下相关知识
limit index,pageSize pageSize:每页显示的条数 pageNum:当前页的页码 index:当前页的起始索引,index=(pageNum-1)*pageSize count:总记录数 totalPage:总页数 totalPage = count / pageSize; if(count % pageSize != 0){ totalPage += 1; } pageSize=4,pageNum=1,index=0 limit 0,4 (当前为第一页,每页数据有4条数据,第一页第一条数据索引为0 pageSize=4,pageNum=3,index=8 limit 8,4 (当前为第三页,每页数据有4条数据,第三页第一条数据索引为2X4=8,前面有两页共8条数据,又因为索引从0开始,所以就是原本为9-1=8.直接2X4=8的规律即index=(pageNum-1)*pageSize pageSize=4,pageNum=6,index=20 limit 20,4 (当前为第六页,每页数据有4条数据,第六页第一条数据索引为5X4=20 首页 上一页 2 3 4 5 6 下一页 末页 (一般的样式都是这样的)
a>添加依赖
com.github.pagehelper pagehelper 5.2.0 b>配置分页插件
在MyBatis的核心配置文件中配置插件(注意标签位置,有顺序的)
2、分页插件的使用
public void testPage(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); //查询功能之前开启分页功能 Page
a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
pageNum:当前页的页码
pageSize:每页显示的条数当我们打印输出一下page的值时可以看到关于分页的相关数据
b>在查询获取list集合之后,使用PageInfo
pageInfo = new PageInfo<>(List list, int
navigatePages)获取分页相关数据public void testPage(){ SqlSession sqlSession = SqlSessionUtil.getSqlSession(); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); //查询功能之前开启分页功能 Page
list:分页之后的数据
navigatePages:导航分页的页码数如下
表示的泛型为当前数据要转化的实体类类型 (上图最后面只输出了1-4范围empid的数据,因为上上面代码执行的查询结果就是输出四条)
c>分页相关数据(常用数据):
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
(这是为后面实现ssm整合时提的基础知识,分页的内容)