MyBatis进阶
1.MyBatis日志管理与动态SQL
1.1.MyBatis日志管理
- 日志文件是用于记录系统操作事件的记录文件或文件集合
- 日志保存历史数据,是诊断问题以及理解系统活动的重要依据
MyBatis的底层通过SLF4J,支持logback
1.1.1.导入依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
1.1.2.再次运行
再次运行mybatis的增删查改操作,控制台就会输出日志信息。
我们可以看到我们执行的sql语句(上图倒数第三行)
以及执行SQL语句时我们传入的参数(上图倒数第二行)
和查询返回的条数(上图倒数第一行)
1.1.2.自定义logback日志细则
在项目src/main/resources目录下,新建logback.xml(固定文件名)文件。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- name属性值可以按照需求自定义,class属性,ConsoleAppender表示控制台日志输出器进行配置 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- pattern标签描述日志输出格式 -->
<!--
[%thread] 表示输出日志的线程名
%d{HH:mm:ss.SSS} 表示输出日志的时间
%-5level %level表示日志级别,-5表示以五个字符进行右对齐
%logger{36} %logger表示输出日志的类,{36}表示这段字符最多不超过36个字符
%msg 表示输出的具体日志信息
%n 表示换行
-->
<pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--
日志输出级别(优先级高到低):
error: 错误 - 系统的故障日志
warn: 警告 - 存在风险或使用不当的日志
info: 一般性消息
debug: 程序内部用于调试信息
trace: 程序运行的跟踪信息
-->
<!--
在生产环境时,一般输出info等级以上的日志
在开发环境时,一般输出debug等级以上的日志,方便调试
-->
<root level="debug">
<!-- 引用上文定义的appender,appender的name属性与ref属性一致 -->
<appender-ref ref="console"/>
</root>
<!-- 这段root表示,日志输出在debug等级以上的都使用上文定义的appender进行输出 -->
</configuration>
具体信息参考官网:logback中文网,logback官网
1.2.动态SQL
- 动态SQL是指根据参数数据动态组织SQL的技术
- 比如:购物网站搜索商品是,备用小标签。
例如如下mapper:
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.lhj.mybatis.entity.Goods">
select *
from t_goods
<where>
<if test="categoryId!=null">
and category_id=#{categoryId}
</if>
<if test="currentPrice!=null">
<!--<为小于号的转义字符-->
and current_price < #{currentPrice}
</if>
</where>
</select>
当传入的Map的属性中,不存在categoryId和currentPrice的值,则sqlsession执行该查询时,执行的sql语句为select *
from t_goods,
当传入的map中,存在categoryId的值,不存在currentPrice的值,则sqlsession执行该查询时,执行的sql语句为select *
from t_goods where category_id=#{categoryId},
当传入的map中,不存在categoryId的值,存在currentPrice的值,则sqlsession执行该查询时,执行的sql语句为select *
from t_goods where current_price < #{currentPrice},
当传入的map中,存在categoryId的值,存在currentPrice的值,则sqlsession执行该查询时,执行的sql语句为select *
from t_goods where category_id=#{categoryId} and current_price < #{currentPrice},
除了<select> 标签有<where>标签,<update>标签还有<set>标签
详细:点击这里
2.MyBatis二级缓存
Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。
- 一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存1024条SQL。
- 二级缓存是指可以跨SqlSession的缓存。是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的。
2.1.Mybatis的一级缓存原理(sqlsession级别)
第一次发出一个查询sql,sql查询结果写入sqlsession的一级缓存中,缓存使用的数据结构是一个map。
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息,将查询到的用户信息存储到一级缓存中。
如果中间sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
2.2.二级缓存原理(mapper级别)
二级缓存的范围是mapper级别(mapper同一个命名空间),mapper以命名空间为单位创建缓存数据结构,结构是map。
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
(来源:https://www.jianshu.com/p/d98d6cb61841,我觉得说的很好就copy来了)
2.3.二级缓存运行规则
- 二级开启后默认所有查询操作均使用缓存(useCache=true)
- 写操作commit提交时对该namespace缓存强制清空
- 配置useCache=false可以不用缓存
- 配置flushCache=true代表强制清空缓存
2.4.代码测试(验证运行规则)
2.4.1.测试一级缓存
mapper.xml:
...
<select id="selectById" parameterType="java.lang.Integer" resultType="com.lhj.mybatis.entity.Goods">
select *
from t_goods
where goods_id = #{id}
</select>
...
执行进行两次查春创建两个对象:
@Test
public void testLv1Cache() throws Exception {
SqlSession session = null;
try {
session = MyBatisUtils.getSqlSession();
Goods goods = session.selectOne("goods.selectById", 1603);
Goods goods1 = session.selectOne("goods.selectById", 1603);
System.out.println(goods.hashCode() + ":" + goods1.hashCode());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSqlSession(session);
}
try {
session = MyBatisUtils.getSqlSession();
Goods goods = session.selectOne("goods.selectById", 1603);
Goods goods1 = session.selectOne("goods.selectById", 1603);
System.out.println(goods.hashCode() + ":" + goods1.hashCode());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSqlSession(session);
}
}
运行结果:
结论:我们发现两个goods对象的hash值是一样的,表示两个变量指向相同的内存地址,但是两次生成的goods不是共享的,由于两次执行,之间sqlsession进行过关闭,所以不为同一个sqlsession。因此验证了一级缓存默认开启,且在同一个SqlSession中共享查询结果。
2.4.2.一级缓存测试commit操作
执行进行两次查春创建两个对象:
@Test
public void testLv1Cache() throws Exception {
SqlSession session = null;
try {
session = MyBatisUtils.getSqlSession();
Goods goods = session.selectOne("goods.selectById", 1603);
session.commit();
Goods goods1 = session.selectOne("goods.selectById", 1603);
System.out.println(goods.hashCode() + ":" + goods1.hashCode());
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSqlSession(session);
}
}
运行结果:
结论:我们发现两个对象的hash值不同,因为执行两次查询的之前进行过commit操作,验证了commit强制清空了一级缓存所有已有缓存。
2.5.开启MyBatis二级缓存
在mapper的xml文件中加入以下配置:
<mapper namespace="goods">
<!--开启了二级缓存
eviction是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
1.LRU – 最近最少使用的:移除最长时间不被使用的对象。
2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3.SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
4.WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒 = 10分钟
size 缓存存储上限,用于保存对象或集合(1个集合算1个对象)的数量上限
readOnly 设置为true ,代表返回只读缓存,每次从缓存取出的是缓存对象本身.这种执行效率较高
设置为false , 代表每次取出的是缓存对象的"副本",每一次取出的对象都是不同的,这种安全性较高
-->
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
...
</mapper>
2.5.1.useCache属性
在开启二级缓存之后,<select>标签多了一个 useCache 属性,用来设置该查询是否写入和使用内存,
值可为"true"或“false”,默认为“true”
一般重用性低的查询,设置为“false”,查询结果不写入内存
<select id="showAll" resultType="com.lhj.mybatis.entity.Goods" useCache="false">
select *
from t_goods
order by goods_id desc
limit 10
</select>
在select标签中设置useCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认为true,即使用二级缓存。
2.5.2.flushCache属性
在开启二级缓存之后,<insert>,<delete>,<select>,<update>的标签欧了一个flushCache的属性,用来设置执行完标签内的SQL语句后是否清空一遍二级缓存。
<update id="updateById" parameterType="Integer" flushCache="true">
update t_goods
set title='测试商品abc'
where goods_id = #{id}
</update>
写操作标签(<insert>,<delete>,<update>)的flushCache默认为true,如果设置成false,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
查询操作标签(<select>)的flushCache默认为false,如果<select useCache=“true” flushCache=“true”>的标签,执行完标签内的sql语句之后,该查询结果是不会放入内存的。
3.MyBatis多表级联查询(嵌套查询)
多表关联查询:两个表通过主外键在一条SQL语句完成所有数据的提取。
多表级联查询:通过一个对象,来获取与他关联的另一个对象,执行多条SQL语句。
3.1.一对多关系查询
还是上次的例子 MyBatis入门
已知商品表与商品详情表存在着1对n的关系。
现在我们想要通过查询列出相应商品下的所有详情。
首先创建一个封装商品详情的实体类:
package com.lhj.mybatis.entity;
import java.io.Serializable;
public class GoodsDetail {
private Integer gdId;
private Integer goodsId;
private String gdPicUrl;
private Integer gdOrder;
....
对应get和set方法
}
和一个封装商品信息的实体类:
package com.lhj.mybatis.entity;
import java.io.Serializable;
import java.util.List;
public class Goods implements Serializable {
private Integer goodsId;
private String title;
private String subTitle;
private Object originalCost;
private Object currentPrice;
private Object discount;
private Integer isFreeDelivery;
private Integer categoryId;
private List<GoodsDetail> goodsDetails;//能用来封装一个商品对应的多条商品详情
....
对应get和set方法
}
创建商品详情的mapper文件goods_detail.xml,并且需要在mybatis-config.xml文件中注册该mapper:
<?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="goodsDetail">
<!-- 根据goods_id来查找相应的信息。 -->
<select id="selectByGoodsId"
parameterType="java.lang.Integer"
resultType="com.lhj.mybatis.entity.GoodsDetail">
SELECT *
FROM t_goods_detail
WHERE goods_id = #{id}
</select>
</mapper>
商品信息的mapper文件goods.xml文件中,添加select标签实现目标功能:
<!--
resultMap可用于说明一对多或者多对一的映射逻辑
id 是resultMap属性引用的标志
type 指向One的实体(Goods),即能将查询结果封装起来的实体类
-->
<resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
<!-- 映射goods对象的主键到goods_id字段 -->
<id column="goods_id" property="goodsId"></id>
<!--
collection的含义是,在
select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历得到goods_id字段值,
并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,
将得到的"商品详情"集合赋值给goodsDetails List对象.
-->
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
column="goods_id"/>
</resultMap>
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,1
</select>
测试:
@Test
public void testOneToMany() throws Exception {
SqlSession session = null;
try {
session = MyBatisUtils.getSqlSession();
List<Goods> list = session.selectList("goods.goodsDetails");
for (Goods goods : list) {
System.out.println(goods.getTitle() + ":" + goods.getGoodsDetails().size());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSqlSession(session);
}
}
个人理解:
因为是一对多的关系,所以在goods实体类里新建商品信息列表属性。
因为这个列表属性的值是由查询得到的,没有数据表的字段名与他对应,所以输出的结果需要用resultMap来对应。
难点:
...
<resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
<id column="goods_id" property="goodsId"></id>
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
column="goods_id"/>
</resultMap>
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,1
</select>
...
附加
...
<mapper namespace="goodsDetail">
<select id="selectByGoodsId"
parameterType="java.lang.Integer"
resultType="com.lhj.mybatis.entity.GoodsDetail">
SELECT *
FROM t_goods_detail
WHERE goods_id = #{id}
</select>
</mapper>
...
对xml标签的理解:
先执行<select>标签,输出结果用resulMap进行映射。
<result>的type属性表示最后输出的实体类的类型。
<id>用来进行主键映射。
column属性用来输入<select>的查询结果中的字段名,property属性用来输入type实体类中的字段名,表示输出结果中的这个字段名的值赋值给实体类中的这个属性。
<collection>标签中:用于一对多关系时使用,collection为集合的意思,意为输出结果为数据集合
<collection>标签中,property属性作用同上,值为"goodsDetails"是因为商品详情的查询结果要赋值给实体类Goods的goodsDetails属性。select属性用来指定要执行查询的唯一标识符"goodsDetail.selectByGoodsId"(命名空间.查询标签ID),column属性填写要传入查询的参数对应的字段名,案例里面<select id=“selectByGoodsId”>需要传入goods_id字段的值来进行查询。column="goods_id"这个column指的是select * from t_goods limit 0,1
的查询结果中的goods_id字段。
以上为个人理解,可能并非正确,具体参考官方文档:MyBatis高级结果映射
3.1.多对一关系查询
已知商品表与商品详情表存在着1对n的关系。
现在我们想要通过查询商品详情列出相应的商品信息。
先修改商品详情实体类,增加用来存放商品信息的属性以及对应的get和set方法:
private Integer gdId;
private Integer goodsId;
private String gdPicUrl;
private Integer gdOrder;
....
对应get和set方法
商品详情的mapper文件goods_detail.xml文件中,添加select标签实现目标功能:
<resultMap id="rmGoodsDetail" type="com.lhj.mybatis.entity.GoodsDetail">
<id property="gdId" column="gd_id"></id>
<result property="goodsId" column="goods_id"/>
<association property="goods" select="goods.selectById" column="goods_id"/>
</resultMap>
<select id="selectGoods" resultMap="rmGoodsDetail">
select *
from t_goods_detail
limit 1,20
</select>
顺带贴上goods.selectById的代码:
<mapper namespace="goods">
...
<select id="selectById" parameterType="java.lang.Integer" resultType="com.lhj.mybatis.entity.Goods">
select *
from t_goods
where goods_id = #{id}
</select>
...
</mapper>
测试:
public void testManyToOne() throws Exception {
SqlSession session = null;
try {
session = MyBatisUtils.getSqlSession();
List<GoodsDetail> goodsDetails = session.selectList("goodsDetail.selectGoods");
for (GoodsDetail g : goodsDetails) {
System.out.println(g.getGdPicUrl() + "" + g.getGoods().getTitle());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSqlSession(session);
}
}
个人理解:
多对一关系查询与一对多关系查询原理差不多
只有以下两点区别:
1.用的标签不同:
一对多关系进行查询时候,<select>里用的是<collection>进行嵌套查询,返回数据集合。
多对一关系进行查询时候,<select>里用的是<association>进行嵌套查询,返回单条数据。
association是联合的意思。
2.返回的实体可能存在属性空值的情况
造成原因:结果映射的时候把原查询结果的某个字段的值优先用于<association>标签进行嵌套查询,以至于原实体类中相应字段的属性造成空值。
解决办法:只要在<association>标签前再用<result>标签进行映射。
个人理解:之所以一对多查询时不存在空值问题情况,可能是一般一对多查询是用原查询的主键字段作为嵌套查询的参数,而主键字段在结果映射里用<id>赋值过,而多对一查询一般是用原查询的非主键字段作为嵌套查询的参数,所以造成空值。
4.分页插件PageHelper
用来便捷分页列出所有的数据,官网:PageHelper
4.1.使用流程
4.1.1.maven引入PageHelper与jsqlparser
可以通过maven引入依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>3.1</version>
</dependency>
4.1.1.mybatis-config.xml增加Plugin配置
编辑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>
...
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<property name="reasonable" value="true"/>
</plugin>
</plugins>
...
</configuration>
interceptor属性用于配置拦截器插件,新版拦截器是 com.github.pagehelper.PageInterceptor
<property>用来添加分页插件参数,具体参考官方文档
helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
4.1.1.代码中使用PageHelper.startPage()自动分页
先写一个列出数据的查询:
<select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods where current_price < 1000
</select>
实现:
@Test
/**
* PageHelper分页查询
*/
public void testSelectPage() throws Exception {
SqlSession session = null;
try {
session = MyBatisUtils.getSqlSession();
/*startPage方法会自动将下一次查询进行分页
第一个参数表示要擦寻第几页的数据
第二个参数表示一页显示几条数据
*/
PageHelper.startPage(2,10);
Page<Goods> page = (Page) session.selectList("goods.selectPage");
System.out.println("总页数:" + page.getPages());
System.out.println("总记录数:" + page.getTotal());
System.out.println("开始行号:" + page.getStartRow());
System.out.println("结束行号:" + page.getEndRow());
System.out.println("当前页码:" + page.getPageNum());
List<Goods> data = page.getResult();//当前页数据
for (Goods g : data) {
System.out.println(g.getTitle());
}
System.out.println("");
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSqlSession(session);
}
}
4.2.不同数据库分页的实现原理
4.2.1.MySQL分页
select * from table limit 10,20;
表示从第10行开始,选取之后20行数据
4.2.2.Oracle分页
select t3.* from (
select t2.*, rownum as row_num from (
select * from table order by id asc
) t2 where rownum<=20
) t3
where t2.row_num>11
第三行是核心查询语句,取行号小于20的数据,获取从第12行开始的数据
4.2.3.SQL Server 2000分页
select top 3 * from table
where
id not in
(select top 15 id from table)
表示从第16行开始,选取之后3行数据
4.2.3.SQL Server 2012+分页
select * from table order by id
offset 4 rows fetch next 5 rows only
表示从第5行开始,选取之后5行数据
5.MyBatis整合C3P0连接池
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。
导入依赖:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
创建C3P0与MyBatis兼容使用的数据源工厂类:
package com.lhj.mybatis.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
//继承UnpooledDataSourceFactory类,使MyBatis能够良好支持
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
public C3P0DataSourceFactory() {
//dataSource 属性来自父类,ComboPooledDataSource类由c3p0提供符合规格的数据源
this.dataSource = new ComboPooledDataSource();
}
}
修改mybatis-config.xml配置文件:
...
<environment id="dev">
<transactionManager type="JDBC"></transactionManager>
<!-- dataSource的type指向新建的数据源工厂类 -->
<dataSource type="com.lhj.mybatis.datasource.C3P0DataSourceFactory">
<!-- 更改数据源类型之后,其属性的name的值有所改变 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3309/babytun?useUnicode=true&characterEncoding=UTF-8"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
<!-- 配置连接信息 -->
<!-- initialPoolSize:初始化时连接池的连接数量 -->
<property name="initialPoolSize" value="5"/>
<!-- maxPoolSize:连接池的最大连接数量 -->
<property name="maxPoolSize" value="20"/>
<!-- minPoolSize:连接池的最小连接数量,一般与初始化相等 -->
<property name="minPoolSize" value="5"/>
<!--...-->
</dataSource>
</environment>
...
6.MyBatis批处理
6.1.批量插入标签:
<!-- 一般批量INSERT语句是这样的:
INSERT INTO table VALUES ("a" , "a1" , "a2"),("b" , "b1" , "b2"),(....) -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
</foreach>
</insert>
<foreach>标签属性:
collection:必须指定,
- 当传入的参数为list类型,为list
- 当传入的参数为array类型,为array
- 当传入的参数为map类型,为map的键key(较少使用)
item:迭代集合时,元素的别名
index:当foreach遍历的数list或=者数组时,index代表就是下标,foreach遍历map时index代表key,此时item表示value。
separator:设置被迭代元素之间的分割符
6.2.批量删除标签:
<!-- 一般批量DELETE语句是这样的:
DELETE FROM table WHERE Id in ("a" , "a1" , "a2") -->
<delete id="batchDelete" parameterType="java.util.List">
DELETE FROM t_goods WHERE goods_id in
<foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
<foreach>标签属性:
open:包裹被迭代集合元素的开始符号。
close:包裹被迭代集合元素的结束符号。
6.3.批量操作存在的问题
1.无法获得插入的数据的id
2.批量生产的SQL太长,可能会被服务器拒绝
7.MyBatis注解开发
注释开发一般用于简单sql语句的开发。
7.1.常用注解
7.2.开发
7.2.1.创建注解开发接口
一般在项目下创建一个名为dao的包,专门用来放注解开发接口
package com.lhj.mybatis.dao;
public interface GoodsDAO {
}
7.2.2.配置mybatis-config.xml文件
有两种配置方法:
一种是用<mapper>标签,用<mapper>的class属性指向具体的用于注释开发的接口类
一种是用<package>标签,用<package>的name的属性指向用来放注解接口类的包,运行时mybatis会自动扫描类下的有注释的接口来应用
...
<configuration>
...
<mappers>
<mapper class="com.lhj.mybatis.dao.GoodsDAO"/>
//上下两种只要选一种写就好了
<package name="com.lhj.mybatis.dao"/>
</mappers>
</configuration>
7.2.3.开发注释接口
7.2.3.1.查询
@Select("Select * from t_goods where current_price between #{min} and #{max} limit 0,#{limit}")
public List<Goods> selectByRange(@Param("min") float min, @Param("max") float max, @Param("limit") int limit);
@Select(" “)中填写SQL语句
方法的返回值为查询结果,方法的参数为传入SQL语句的参数,带参方法前用@Param(” ")指定SQL语句中对应的元素。
实现:
@Test
public void selectByRange() {
SqlSession session = null;
try {
session = MyBatisUtils.openSession();
//得到映射器,传入带有Mybatis注解的接口类
//在运行时,session会根据接口类中的配置信息动态生成实现类
GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
//调用实现类中的方法完成SQL查询
List<Goods> goods = goodsDAO.selectByRange(100, 500, 20);
for (Goods g : goods) {
System.out.println(g.getTitle());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.closeSession(session);
}
}
7.2.3.2.插入
@Insert("INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id) VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})")
@SelectKey(statement = "SELECT last_insert_id()", keyProperty = "good_id", before = false, resultType = Integer.class)
public int insert(Goods goods);
@SelectKey()注解用来返回插入后的插入记录数的id
有四个必须的属性:
statement = , 用来填写返回id的sql语句
keyProperty = , 用来指定主键的字段名
before = , 是否输出执行插入语句前的id值
resultType = 用来指定返回id的类型,例如Integer.class
7.2.3.2.结果映射
也可以参考一下这个点击这里
@Select("Select goods_id,title,current_price from t_goods limit 0,20")
@Results({
@Result(column = "goods_id", property = "goodsId", id = true),
@Result(column = "title", property = "title"),
@Result(column = "current_price", property = "currentPrice")
})
public List<GoodsDTO> selectAll();
@Results()相当于xml文件中的<resultMap>,{ }表示一个集合用来放@Result( )注解
@Result( )相当于xml文件中的<id>或者
column 数据库的列名
Property需要装配的属性名
主键映射时,相当于用<id>映射主键时,用@Result( )的 id 属性来设置
7.2.3.2.嵌套查询结果映射
@Result(column=" ",property="",one=@One(select=""))
@One注解(一对一)代替了标签,在注解中用来指定子查询返回单一对象。@One里的select属性用来指向另一个抽象接口的查询方法。
@Result(property="",column="",many=@Many(select=""))
@Many(多对一)代替了标签,在注解中用来指定子查询返回对象集合。select属性用来指向另一个抽象接口的查询方法。