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">
            	<!--&lt;为小于号的转义字符-->
                and current_price &lt; #{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 &lt; 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&amp;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属性用来指向另一个抽象接口的查询方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值