【Mybatis】Bonus01 笔记资料
对原生JDBC程序的问题总结
public void jdbc() { // 声明Connection对象 Connection con;
// 驱动程序名 String driver = "com.mysql.jdbc.Driver"; // URL指向要访问的数据库名myschool String url = "jdbc:mysql://localhost:3306/myschool"; // MySQL配置时的用户名 String user = "root"; // MySQL配置时的密码 String password = "123456";
// 遍历查询结果集 try { // 加载驱动程序 Class.forName(driver); // 1.getConnection()方法,连接MySQL数据库!! con = DriverManager.getConnection(url, user, password); // 2.创建statement类对象,用来执行SQL语句!! // 要执行的SQL语句 String sql = "select * from student where studentNo=?"; PreparedStatement statement = con.prepareStatement(sql); statement.setObject(1, 10000); // 3.ResultSet类,用来存放获取的结果集!! ResultSet rs = statement.executeQuery(); System.out.println("-----------------"); System.out.println("姓名" + "\t" + "地址"); System.out.println("-----------------"); String address = null; String studentName = null; while (rs.next()) { // 获取address这列数据 address = rs.getString("address"); // 获取studentName这列数据 studentName = rs.getString("studentName"); // 输出结果 System.out.println(studentName + "\t" + address); } rs.close(); con.close(); } catch (ClassNotFoundException e) { // 数据库驱动类异常处理 System.out.println("Sorry,没有找到驱动文件"); e.printStackTrace(); } catch (SQLException e) { // 数据库连接失败异常处理 e.printStackTrace(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } finally { System.out.println("数据库数据成功获取!!"); } }
问题总结
1、数据库连接,使用时创建,不使用就立刻释放,对数据库进行频繁的开启和关闭。
影响数据库性能
处理方案:使用数据库连接池管理数据库连接
2:将Sql语句硬编码到java代码中,如何sql语句修改,需要重新编译java代码,不利于系统维护
处理方案:
将sql语句配置到xml文件中,就算sql变化了,也不需要重新对java代码进行重新编译。
3:向PreparedStatement中设置参数,对占位符和设置参数值,都是硬编码在java代码中,不利于系统维护。
处理方案:
将sql语句和占位符以及参数全部配置在xml中。
4:从resutSet中遍历结果集,存在硬编码,将表的字段进行硬编码,不利于系统维护
处理方案:
将查询的结果集,自动的映射成java对象
Mybatis框架
Mybatis是什么?
Mybatis是一个持久层的框架,Mybatis让程序员将主要精力放在sql上,
通过Mybatis提供的映射方式,自由灵活生产(半自动化)满足需求的sql语句
Mybatis可以将PreparedStatement中的输入参数自动进行输入映射,
讲查询结果集灵活映射成java对象(输出映射)
1:SqlMapConfig.xml
此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息(数据源,事务等)。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句,
此文件需要在SqlMapConfig.xml中加载。
2:通过mybatis环境等配置信息
构造SqlSessionFactory(即会话工厂)。
作用:创建SqlSession
3:由会话工厂创建sqlSession即会话,
操作数据库需要通过sqlSession进行。
作用:操作数据库(操作sql的添加,修改,删除,查询等)
4:mybatis底层自定义了Executor执行器接口操作数据库
Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
作用:SqlSession内部通过执行器操作数据库
5:MappedStatement也是mybatis一个底层封装对象
它包装了mybatis配置信息及sql映射信息等。
mapper.xml文件中一个sql对应一个MappedStatement对象,
sql的id即是MappedStatement的id。
作用:对操作数据库存储封装,包括sql语句,输入参数,输出结果类型
6:MappedStatement对sql执行输入参数进行定义
包括HashMap、基本类型、pojo,
Executor通过MappedStatement在执行sql前将输入的java对象映射至sql中,
输入参数映射就是JDBC编程中对preparedStatement设置参数。
7:MappedStatement对sql执行输出结果进行定义
包括HashMap、基本类型、pojo,Executor
通过MappedStatement在执行sql后将输出结果映射至java对象中,
输出结果映射过程相当于JDBC编程中对结果的解析处理过程。
快速入门 Quickstart
Mybatis-3.2.2.jar
Mysql-connector-java-5.10-bin.jar
也就是Mybatis组件 和 JDBC驱动包足以
但是这里还需要一个日志配置文件,log4j.properties
SqlMapConfig.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"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/myschool" /> <property name="username" value="root" /> <property name="password" value="123456" /> </dataSource> </environment> </environments> </configuration>
1 根据Id查询数据
XXXmapper.xml
在SqlMapConfig.xml加载映射文件
编写程序
添加用户的SQL
#{} 和${}
selectOne 和selectList
1.1 原始dao开发
程序员自己需要dao接口 和dao的实现类
需要往dao实现类中注入SqlSessionFactory ,在方法体中通过SqlSessionFactory创建SqlSession
1.1.1 Dao接口
1.1.1 Dao接口的实现类
1.1.1 测试类
原始Dao开发的总结
1:dao接口实现类方法中存在大量模板方法
2:调用sqlSession方法时将statement的ID硬编码了
3:调用sqlSession方法时传入的变量,由于sqlsession方法使用泛型,及时变量类型传入错误,在编译阶段也不报错,不利于程序开发
Mapper代理方法
程序员需要编写mapper.xm映射文件
程序员在编写mapper接口的时候需要遵循一些开发规范,mybatis才可以自动生成mapper接口实现类的代理对象。
开发规范:
1:在mapper.xml中namespace等于mapper接口地址
2:mapper.java接口中的方法名和mapper.xml中statement的id一致
3:mapper.java接口中的方法输入参数类型和mapper.xml中的statement的parameterType指定的类型一致。
4:mapper.java接口中的方法返回值类型和mapper.xml中的statement的resultType指定的类型一致。
mapper.java
mapper. xml
SqlMapConfig.xml加载映射文件
Mapper接口方法参数只能有一个
Mapper接口方法参数只能有一个,是否影响系统的维护
在系统框架中,dao层的代码是被业务层共用的
就算mapper接口只有一个参数,也可以使用包装类型的pojo满足不同业务方法的需求。
持久层方法的参数可以是包装类型,map等,在service方法中建议不要使用map这样的 类型
Setting全局参数配置
Mybatis框架在运行时可以调整一些运行参数
比如:开启二级缓存,开启延时加载…
全局参数将会影响到mybatis的运行行为
<!--设置 --> <settings> <!--缓存配置的全局开关:如果这里设置成false,那么即便在映射器中配置开启也无济于事 --> <setting name="cacheEnabled" value="true" /> <!--延时加载的全局开关 --> <setting name="lazyLoadingEnabled" value="false" /> <!-- 是否允许单一语句返回多结果集 --> <setting name="multipleResultSetsEnabled" value="true" /> <!-- 使用列标签代替列名,需要兼容驱动 --> <setting name="useColumnLabel" value="true" /> <!-- 允许JDBC自动生成主键,需要驱动兼容。如果设置为true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍能正常工作 --> <setting name="useGeneratedKeys" value="false" /> <!-- 指定MyBatis该如何自动映射列到字段或属性:NONE表示取消自动映射;PARTIAL表示只会自动映射,没有定义嵌套结果集和映射结果集;FULL会自动映射任意复杂的结果集,无论是否嵌套 --> <setting name="autoMappingBehavior" value="PARTIAL" /> <!-- 配置默认的执行器:SIMPLE是普通的执行器;REUSE会重用预处理语句;BATCH会重用语句并执行批量更新 --> <setting name="defaultExecutorType" value="SIMPLE" /> <!--设置超时时间:它决定驱动等待数据库响应的秒数,任何正整数--> <setting name="defaultStatementTimeout" value="25"/> <!--设置数据库驱动程序默认返回的条数限制,此参数可以重新设置,任何正整数 --> <setting name="defaultFetchSize" value="100" /> <!-- 允许在嵌套语句中使用分页(RowBounds) --> <setting name="safeRowBoundsEnabled" value="false" /> <!-- 是否开启自动驼峰命名规则,即从a_example到aExample的映射 --> <setting name="mapUnderscoreToCamelCase" value="true" /> <!-- 本地缓存机制,防止循环引用和加速重复嵌套循环 --> <setting name="localCacheScope" value="SESSION" /> <!-- 当没有为参数提供特定JDBC类型时,为空值指定JDBC类型。某些驱动需要指定列的JDBC类型,多数情况直接用一般类型即可,如NULL/VARCHAR/OTHER --> <setting name="jdbcTypeForNull" value="OTHER" /> <!-- 指定触发延迟加载的方法,如equals/clone/hashCode/toString --> <setting name="lazyLoadTriggerMethods" value="equals" /> </settings>
输入映射
通过parameterType指定输入参数的类型,类型可以是简单类型,hashmap和pojo的包装类型
Pojo的包装对象
功能需求
根据学生信息综合查询,需要传入查询条件比较复杂,可能包括学号,姓名或其他信息,如班级名称,年级等。
定义包装类pojo
根据需求,建议使用自定义的包装类型的pojo
在包装类型中,我们讲复杂的查询条件包装进去
输出映射
resultTyle
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo中的属性名全部不一致,没有创建pojo对象
只要查询出来的列名和pojo中的属性有一个一致,就会创建pojo对象
resultMap 属性解析
resultMap的使用方法
如果查询出来的列名和pojo的属性名不一致,
通过定义一个resultMap对列名和pojo属性名之间做一个映射关系。
(1) 定义resultMap
(2)使用resultMap作为statement的输出类型
使用
resultMap的使用方法
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间做一个映射关系。
(1) 定义resultMap
(2)使用resultMap作为statement的输出类型
使用
一对一查询
resultType
sql语句
SELECT a.*, b.`gradeName` FROM `student` a LEFT JOIN `grade` b ON a.`gradeId`=b.`gradeID`
POJO
resultMap
sql
sql属于和resultType一样
使用resultMap的思路
使用resultMap将查询的结果中年级信息映射到Student对象中,
在Student类中添加grade属性,
将关联查询出来的年级信息映射到Student对象中的grade属性中
需要Student类中添加grade属性:
Mapper.xml
Mapper.java
resultMap & resultType 实现一对一的总结
resultType:
使用resultType实现简单,如果pojo中没有包括查询出来的列名,
只需添加队名对应的属性,就可以完成映射,
如果没有特殊要求建议使用resultType
resultMap:
需要单独定义resultMap,实现有点麻烦,
如果对查询结果有特殊要求,
可以使用resultMap将关联查询映射到pojo属性中。
resultMap可以实现延时加载,resultType无法实现延时加载
一对多查询
需求:
查询班级下所有学生
SQL
SELECT a.`gradeName`, b.* FROM `grade` a , `student` b WHERE a.`gradeID`=b.`gradeId`
POJO
Mapper.xml
懒加载
什么是延时加载
resultMap可以实现高级映射association和collection,
association & collection 具备延时加载功能
延时加载:
先从单表查询,当需要时再从关联表中去关联查询
从而提高了数据库性能
1.2 Mapper.xml
需要定义两个mapper的方法的statement
(1)只查询班级信息
SELECT * FROM grade
(2)关联查询学生信息
通过上面查询的年级信息中的gradeID去关联查询学生信息
延时加载的配置
Mybatis默认没有开启延时加载
需要在全局配置文件中的settings中配置
<settings> <!--开启延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> <!--关闭积极加载--> <setting name="aggressiveLazyLoading" value="false"/> </settings>
查询缓存
什么是查询缓存
缓存的有两方法
(1) 提高运行效率 (重复运行的时间)
(2) 解决对象构建复杂的过程(把好不容易实例话的复杂对象保存下来,下次直接用)
Mybaits提供查询缓存,用于减轻数据库压力,提高数据库性能
Mybaits提供一级缓存和二级缓存
在操作数据库时需要构造SqlSession对象,
在对象中有一个数据结构(HashMap)用于缓存数据。
不同的SqlSession之间的缓存数据区域是互相不影响的。
Mybatis一级缓存的作用域是同一个SqlSession,
在同一个sqlSession中两次执行相同的sql语句,
第一次执行完毕会将数据库中查询的数据写到缓存(内存),
第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的
其作用域是mapper的同一个namespace,
不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,
第一次执行完毕会将数据库中查询的数据写到缓存(内存),
第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
一级缓存
第一次发起查询,先去找缓存中是否有id为1的用户信息,
如果没有,从数据库中查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
第二次发起查询用户id为1的用户信息,先去缓存中是否有id为1的用户信息,
缓存中有,直接从缓存中获取用户信息。
如果SqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存。
目的是为了让缓存中存储的是最新的信息,避免脏读。
Mybatis默认支持一级缓存,不需要在配置文件中配置。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。
value为从查询出来映射生成的java对象。
实战应用
二级缓存
SqlSession1去查询用户id为1的用户信息,
查询到用户信息会将查询数据存储到二级缓存中。
SqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,
如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。
value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
注意:
需要关闭一级缓存,才会把一级缓存放到二级缓存,
所以sqlSession.close() 这句代码执行后,
再通过sqlsession1进行查询,会使用二级缓存。
如果你把这句语句也放在finally语句里的话,二级缓存是没起作用的。
缓存的顺序是先检查二级缓存,然后一级缓存,没有才去查询数据库的。
开启二级缓存
在核心配置文件mybatis-config.xml中加入
<setting name="cacheEnabled" value="true"/>
要在你的Mapper映射文件中添加一行:
<cache />
表示此mapper开启二级缓存
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作
注意如果存在父类、成员pojo都需要实现序列化接口。
pojo对象实现java.io.Serializable
为了将缓存数据取出执行反序列化,因为二级缓存存储介质多种多样,不一定在内存。
【我的建议:补上序列号,防止序列化后类结构改变,反序列化类型恢复失败】
禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句的二级缓存,
即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findStudnetListResultMap" resultMap="ordersUserMap" useCache="false">
分布式缓存
通过实现Cache接口可以实现mybatis缓存数据通过其它缓存数据库整合,
mybatis的特长是sql操作,缓存数据的管理不是mybatis的特长,
为了提高缓存的性能将mybatis和第三方的缓存数据库整合,
比如ehcache、memcache、redis等。
整合方法
mybatis提供二级缓存Cache接口,
如果要实现自己的缓存逻辑,就要实现Cache接口即可
默认实现类
Mapper.xml
动态sql
什么是动态sql
Mybatis对sql语句灵活的操作,通过表达式表达式进行判断,对sql进行灵活拼接。
IF判断
Mapper.xml
测试
Sql片段
概念
将动态sql中的判断等重复的sql抽取出来,组成一个sql片段,
其他的sql就可以引用这个sql片段
Mapper.xml
Foreach
向sql传递数组或list,mybatis使用foreach解析
需求
Sql 中的in 查询 等等
测试
Sql指令
https://www.cnblogs.com/ashleyboy/p/9271597.html
if 语句 (简单的条件判断)
if标签用来实现根据条件拼接sql语句,下面示例用来判断参数如果不为null,则拼接sql
<select id="ifTest" resultType="com.sl.po.Product"> select * from products where <if test="ProductName!=null"> name like #{ProductName} </if> <if test="description!=null"> and description like CONCAT(CONCAT('%', #{Description, jdbcType=VARCHAR}),'%') </if> </select>
choose (when,otherwize)
相当于java 语言中的 switch ,与 jstl 中的choose 很类似
trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)
trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,
也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix;
可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,
对应的属性是 prefixOverrides 和 suffixOverrides;
正因为 trim 有这样的功能,它可以用来实现 where 和 set 的效果。
where标签示例,此处使用trim代替: <!-- if+trim 使用trim代理where--> <select id="trimwhereTest" resultType="com.sl.po.Product"> select * from products <!-- <where> <if test="Name!=null"> and name like #{Name} <!--name like #{Name}--> </if> <if test="description!=null"> and description like #{Description} </if> </where> --> <!-- 移除首部所有指定在 prefixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容--> <trim prefix="WHERE" prefixOverrides="AND |OR"> <if test="Name!=null"> and name like #{Name} </if> <if test="description!=null"> and description like #{Description} </if> </trim> </select> 前面set标签示例,此处使用trim代替: <!--if+trim 代替 使用trime代替set --> <update id="trimsetTest"> update products <!-- <set> <if test="cityCode!=null"> citycode = #{cityCode} , </if> <if test="Name!=null"> name = #{Name} , </if> <if test="description!=null"> description = #{Description} </if> </set> --> <!-- 移除尾部所有指定在 suffixOverrides 属性中的内容,并且插入 prefix 属性中指定的内容--> <trim prefix="set" suffixOverrides=","> <if test="cityCode!=null and cityCode!=''"> citycode = #{cityCode} , </if> <if test="Name!=null"> name = #{Name} , </if> <if test="description!=null"> description = #{Description} </if> </trim> where id=#{id} </update>
where
(主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误)
当 where 中的条件使用的 if 标签较多时,这样的组合可能会导致错误,
“where”标签会自动判断如果它包含的标签中有返回值的话,就在sql中插入一个‘where’,
如果where标签最后返回的内容是以 and 或者or 开头的,也会被自动移除掉。
<select id="whereTest" resultType="com.sl.po.Product"> select * from products <!-- where标签自动移除第一个and--> <where> <if test="Name!=null"> and name like #{Name} <!--name like #{Name}--> </if> <if test="description!=null"> and description like #{Description} </if> </where> </select>
set (主要用于更新时)
set 标签是用在更新操作的时候,功能和 where 标签元素差不多,
主要是在包含的语句前输出一个 set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,
如果 set 标签最终返回的内容为空的话则可能会出错(update table where id=1)
<!-- if + set 实现按条件更新--> <update id="setTest"> update products <!-- set标签将移除最后一个“,” --> <set> <if test="cityCode!=null"> citycode = #{cityCode} , </if> <if test="Name!=null"> name = #{Name} , </if> <if test="description!=null"> description = #{Description} , </if> </set> where id =#{id} </update>
foreach (在实现 mybatis in 语句查询时特别有用)
mybatis提供foreach标签,用来对一个集合进行遍历,
通常是用来构建 IN 条件语句,也可用于其他情况下动态拼接sql语句。
foreach标签有以下几个属性collection, item,index,open,separator,close。
1. collection表示需要遍历的集合
2. item 表示每次遍历时生成的对象名
3. index表示在迭代过程中,每次迭代到的位置)
4. open表示开始遍历时要拼接的字符串
5. separator表示在每次遍历时两个对象直接的连接字符串
6. close表示结束遍历时要拼接的字符串
当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
在使用foreach的时候针对不同的参数类型,
collection属性值要分为以下3种情况:
1.如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2.如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3.如果传入的参数是多个的时候,我们就需要把它们封装成一个Map或者Object。
<!-- 通过pojo传递list, collection值为pojo中对应的属性名--> <select id="foreachVoTest" resultType="com.sl.po.Product"> select * from products <where> <if test="name!=null"> and name like #{name} </if> <if test="ids!=null"> <foreach item="item" index="index" collection="ids" open="and id in(" separator="," close=")">#{item}</foreach> </if> </where> </select>
摘自张炜老师的笔记