<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
再次运行mybatis的增删查改操作,控制台就会输出日志信息。
我们可以看到我们执行的sql语句(上图倒数第三行)
以及执行SQL语句时我们传入的参数(上图倒数第二行)
和查询返回的条数(上图倒数第一行)
在项目src/main/resources目录下,新建logback.xml(固定文件名)文件。
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%npattern>
encoder>
appender>
<root level="debug">
<appender-ref ref="console"/>
root>
configuration>
具体信息参考官网:logback中文网,logback官网
例如如下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},
除了
Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。
第一次发出一个查询sql,sql查询结果写入sqlsession的一级缓存中,缓存使用的数据结构是一个map。
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息,将查询到的用户信息存储到一级缓存中。
如果中间sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
二级缓存的范围是mapper级别(mapper同一个命名空间),mapper以命名空间为单位创建缓存数据结构,结构是map。
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
(来源:https://www.jianshu.com/p/d98d6cb61841,我觉得说的很好就copy来了)
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中共享查询结果。
执行进行两次查春创建两个对象:
@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强制清空了一级缓存所有已有缓存。
在mapper的xml文件中加入以下配置:
<mapper namespace="goods">
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
...
mapper>
在开启二级缓存之后,
<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,即使用二级缓存。
在开启二级缓存之后,
<update id="updateById" parameterType="Integer" flushCache="true">
update t_goods
set title='测试商品abc'
where goods_id = #{id}
update>
写操作标签(
查询操作标签(
多表关联查询:两个表通过主外键在一条SQL语句完成所有数据的提取。
多表级联查询:通过一个对象,来获取与他关联的另一个对象,执行多条SQL语句。
还是上次的例子 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:
<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>
商品信息的mapper文件goods.xml文件中,添加select标签实现目标功能:
<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>
测试:
@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标签的理解:
先执行
column属性用来输入
select * from t_goods limit 0,1
的查询结果中的goods_id字段。
以上为个人理解,可能并非正确,具体参考官方文档:MyBatis高级结果映射
已知商品表与商品详情表存在着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);
}
}
个人理解:
多对一关系查询与一对多关系查询原理差不多
只有以下两点区别:
一对多关系进行查询时候,
多对一关系进行查询时候,
association是联合的意思。
造成原因:结果映射的时候把原查询结果的某个字段的值优先用于
解决办法:只要在
个人理解:之所以一对多查询时不存在空值问题情况,可能是一般一对多查询是用原查询的主键字段作为嵌套查询的参数,而主键字段在结果映射里用
用来便捷分页列出所有的数据,官网:PageHelper
可以通过maven引入依赖:
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.1.11version>
dependency>
<dependency>
<groupId>com.github.jsqlparsergroupId>
<artifactId>jsqlparserartifactId>
<version>3.1version>
dependency>
编辑mybatis的配制文件:
<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
helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
先写一个列出数据的查询:
<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);
}
}
select * from table limit 10,20;
表示从第10行开始,选取之后20行数据
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行开始的数据
select top 3 * from table
where
id not in
(select top 15 id from table)
表示从第16行开始,选取之后3行数据
select * from table order by id
offset 4 rows fetch next 5 rows only
表示从第5行开始,选取之后5行数据
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。
导入依赖:
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.5.5version>
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="com.lhj.mybatis.datasource.C3P0DataSourceFactory">
<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"/>
<property name="initialPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
<property name="minPoolSize" value="5"/>
dataSource>
environment>
...
<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>
collection:必须指定,
item:迭代集合时,元素的别名
index:当foreach遍历的数list或=者数组时,index代表就是下标,foreach遍历map时index代表key,此时item表示value。
separator:设置被迭代元素之间的分割符
<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>
open:包裹被迭代集合元素的开始符号。
close:包裹被迭代集合元素的结束符号。
1.无法获得插入的数据的id
2.批量生产的SQL太长,可能会被服务器拒绝
注释开发一般用于简单sql语句的开发。
一般在项目下创建一个名为dao的包,专门用来放注解开发接口
package com.lhj.mybatis.dao;
public interface GoodsDAO {
}
有两种配置方法:
一种是用
一种是用
...
<configuration>
...
<mappers>
<mapper class="com.lhj.mybatis.dao.GoodsDAO"/>
//上下两种只要选一种写就好了
<package name="com.lhj.mybatis.dao"/>
mappers>
configuration>
@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);
}
}
@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
也可以参考一下这个点击这里
@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文件中的
@Result( )相当于xml文件中的
column 数据库的列名
Property需要装配的属性名
主键映射时,相当于用
@Result(column=" ",property="",one=@One(select=""))
@One注解(一对一)代替了标签,在注解中用来指定子查询返回单一对象。@One里的select属性用来指向另一个抽象接口的查询方法。
@Result(property="",column="",many=@Many(select=""))
@Many(多对一)代替了标签,在注解中用来指定子查询返回对象集合。select属性用来指向另一个抽象接口的查询方法。