4.Mybatis多表级联查询
多表级联查询和多表关联查询不一样,之前说的关联查询是指两个表通过主外键在一条SQL中完成所有数据的提取,而多表级联查询通常是指通过一个对象来获取与它关联的另外一个对象,执行的SQL语句分为多条。我们都知道,在MySQL等关系型数据库中,他们都有主键和外键,数据的关系也有一对一、一对多和多对多。
例如商品与商品详情对象有一对多的关系,商品是一的一方,详情是多的一方。详情要持有商品的主键。理清楚这个关系之后,我们就可以利用Mybatis的对象关联查询来快捷的获取商品下的所有详情,或者根据某一个详情对象得到与它对应的商品信息。
我们通过案例来体验:
打开t_goods_detail表,这张表是商品的详情信息的表,里面gd_id是自动生成的id,good_id是外键,gd_pic_ur是商品的图片,gd_order代表显示的时候他的排序前后顺序。
然后在entity包下创建t_goods_detail表对应的实体类GoodsDetail:
package org.haiexijun.entity; public class GoodsDetail { private Integer gdId; private Integer goodsId; private String gdPicUrl; private Integer gdOrder; public Integer getGdId() { return gdId; } public void setGdId(Integer gdId) { this.gdId = gdId; } public Integer getGoodsId() { return goodsId; } public void setGoodsId(Integer goodsId) { this.goodsId = goodsId; } public String getGdPicUrl() { return gdPicUrl; } public void setGdPicUrl(String gdPicUrl) { this.gdPicUrl = gdPicUrl; } public Integer getGdOrder() { return gdOrder; } public void setGdOrder(Integer gdOrder) { this.gdOrder = gdOrder; } }
之后,在resources下的mappers文件夹下创建一个goods_detail的xml文件。
<?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"> <select id="selectByGoodsId" parameterType="Integer" resultType="org.haiexijun.entity.GoodsDetail"> select * from t_goods_detail where goods_id= #{value} </select> </mapper>
我们的goods表是一的一方,t_goods_detail是多的一方。所以可以对Goods实体类进行一些更改,加入一个List<GoodsDetail>集合,并赋予get和set方法,表示一个Goods商品对应多个GoodsDetail商品详情。
package org.haiexijun.entity; import java.util.List; public class Goods { private Integer goodId;//商品编码 private String title;//标题 private String subTitle;//子标题 private Float originalCost;//原始的价格 private Float currentPrice;//当前的价格 private Float discount;//折扣率 private Integer isFreeDelivery;//是否包邮。1包邮,0不包邮 private Integer categoryId;//分类编号 private List<GoodsDetail> goodsDetails; //商品详情 public List<GoodsDetail> getGoodsDetails() { return goodsDetails; } public void setGoodsDetails(List<GoodsDetail> goodsDetails) { this.goodsDetails = goodsDetails; } public Integer getGoodId() { return goodId; } public void setGoodId(Integer goodId) { this.goodId = goodId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getSubTitle() { return subTitle; } public void setSubTitle(String subTitle) { this.subTitle = subTitle; } public Float getOriginalCost() { return originalCost; } public void setOriginalCost(Float originalCost) { this.originalCost = originalCost; } public Float getCurrentPrice() { return currentPrice; } public void setCurrentPrice(Float currentPrice) { this.currentPrice = currentPrice; } public Float getDiscount() { return discount; } public void setDiscount(Float discount) { this.discount = discount; } public Integer getIsFreeDelivery() { return isFreeDelivery; } public void setIsFreeDelivery(Integer isFreeDelivery) { this.isFreeDelivery = isFreeDelivery; } public Integer getCategoryId() { return categoryId; } public void setCategoryId(Integer categoryId) { this.categoryId = categoryId; } }
现在我们已经完成了对象的关联,但是呢,作为里面这个List<GoodsDetail>的数据我们并没有进行获取,它里面还是空的。未来解决这个问题,我们还要在goods.xml中对这个List<GoodsDetail>进行说明。
<resultMap id="rmGoods1" type="org.haiexijun.entity.Goods"> <id column="goods_id" property="goodId"></id> <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/> </resultMap> <select id="selectOneToMany" resultMap="rmGoods1"> select * from t_goods limit 0,1 </select>
上面编写了一个<select> ,并指定了resultMap为上面定义的rmGoods1。在对象关联的情况下,我们是通过resultMap来实现的。resultMap里面的collection标签的作用是在 select * from t_goods where goods_id=1 得到结果后,,然后对所有的Goods对象遍历得到goods_id字段值,并带入到goodsDetails命名空间的findByGoodsId的sql中查询,最后将得到的”商品详情“集合赋值给List<GoodsDetail>。
我们不要忘记在mybatis-config.xml这个总配置文件中注册 goods_detail.xml
<mappers> <mapper resource="mappers/goods.xml"/> <mapper resource="mappers/goods_detail.xml"/> </mappers>
在MybatisTest测试类中编写测试方法:
@Test public void testSelectOneToMany(){ SqlSession session=null; try { session=MybatisUtils.openSession(); Goods goods= session.selectOne("goods.selectOneToMany"); List<GoodsDetail> gd=goods.getGoodsDetails(); }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(session); } }
上面这个案例我们实现了一对多的查询。
下面我要来体验一下多对一的查询。如果我得到了一个GoodsDetail描述对象,想得到相对应的商品信息。就要使用Mybatis的多对一对象关联查询了。多的一方想要得到一的一方,只要在多的一方增加一的一方的实体就可以了。
在GoodsDetail类里面加入Goods属性,同时设置get和set方法。
package org.haiexijun.entity; public class GoodsDetail { private Integer gdId; private Integer goodsId; private String gdPicUrl; private Integer gdOrder; private Goods goods; public Goods getGoods() { return goods; } public void setGoods(Goods goods) { this.goods = goods; } public Integer getGdId() { return gdId; } public void setGdId(Integer gdId) { this.gdId = gdId; } public Integer getGoodsId() { return goodsId; } public void setGoodsId(Integer goodsId) { this.goodsId = goodsId; } public String getGdPicUrl() { return gdPicUrl; } public void setGdPicUrl(String gdPicUrl) { this.gdPicUrl = gdPicUrl; } public Integer getGdOrder() { return gdOrder; } public void setGdOrder(Integer gdOrder) { this.gdOrder = gdOrder; } }
上一步增加了Goods属性,但是Mybatis并不直到它是从哪来的。我们需要打开goods_detail.xml进行关联的描述:
<resultMap id="rmGoodsDetail" type="org.haiexijun.entity.GoodsDetail"> <id column="gd_id" property="gdId"></id> <association column="good_id" property="goods" select="goods.selectById" ></association> </resultMap> <select id="selectManyToOne" resultMap="rmGoodsDetail"> select * from t_goods_detail limit 0,1 </select>
编写测试方法:
@Test public void testSelectManyTOne(){ SqlSession session=null; try { session=MybatisUtils.openSession(); GoodsDetail gd= session.selectOne("goodsDetail.selectManyToOne"); System.out.println(gd); }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(session); } }
5.分页插件----PageHelper
PageHelper是一款由中国人开发的Mybatis分页插件,该分页插件支持任何复杂的单表、多表分页。PageHelper官网:https://pagehelper.github.io/
因为这个插件的官方文档非常的详细和清晰,所以下面只做一个简单的案例来体验一下这个插件,以后如果有其他的需求,就去参考官方文档。
PageHelper的使用流程
1.引入maven依赖PageHelp和jsqlparser
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.0</version> </dependency>
2.mybatis-config.xml增加Plugin配置
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
3.代码中使用PageHelper.startPage()来完成自动分页
先在goods.xml中编写一条查询语句:
<select id="selectPage" resultType="org.haiexijun.entity.Goods"> select * from t_goods where current_price < 1000 </select>
然后再测试类中编写测试方法使用:
@Test public void testSelectPage(){ SqlSession session=null; try { session=MybatisUtils.openSession(); //开启分页,startPage方法会自动将下一次查询进行分页,第一个参数为第几页的数据,第二个参数为每一页有多少行 //下面查询第10-20条数据 PageHelper.startPage(2,10); //下面这个查询要注意,以前返回的是一个List集合,但这里返回的是一个配置对象,包含分页数据和分页信息 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){ e.printStackTrace(); }finally { MybatisUtils.closeSession(session); } }
6.Mybatis配置C3P0连接池
关于C3P0连接池的基本使用,我之前有写过一篇博客:C3P0连接池的基本配置与使用 , 里面有比较详细的基本使用步骤。
不过这里还是会教大家具体的使用步骤的。
1.再pom.xml中导入C3P0连接池需要的依赖(c3p0-0.9.1.2.jar和mchange-commons-java.jar)
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency> <!-- https://mvnrepository.com/artifact/c3p0/c3p0 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency>
2.在org.haiexijun包下创建一个dataSource文件夹,里面创建一个C3P0DataSourceFactory类,这个类是我们自定义C3P0的数据源工厂。创建好了以后,为了让Mybatis对他有良好的支持,这个类需要继承自一个父类UnpooledDataSourceFactory,虽然这个父类名叫UnpooledDataSourceFactory,但请不要被这个父类名给迷惑误解了,通过继承这个类可以完成C3P0的嵌入工作。
package org.haiexijun.dataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory; /** * C3P0与Mybatis兼容使用的数据源工厂类 */ public class C3P0DataSourceFactory extends UnpooledDataSourceFactory { public C3P0DataSourceFactory(){ //让数据源指向对应的连接池 this.dataSource=new ComboPooledDataSource(); } }
3.更改mybatis-config.xml中的dataSources的type,由之前的POOLEDED改为我们C3P0连接池。这里还要注意一个点,dataSource里面的属性property的有些配置和C3P0的有些不一样,我们需要对其进行更改。
<!--采用连接池的方式管理数据库连接--> <dataSource type="org.haiexijun.dataSource.C3P0DataSourceFactory"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun"/> <property name="user" value="root"/> <property name="password" value="zc20020106"/> <property name="initialPoolSize" value="5"/> <property name="maxPoolSize" value="20"/> </dataSource>
3.最后,就可以使用C3P0了
7.Mybatis批处理
批处理其实就是批量处理的意思,之前的案例我们的增删改查都是一条一条地被处理的。但实际工作中,很多情况下,一条一条去处理,效率是很慢的。比方说,假设我有10万条数据,按照之前的语句一条一条来处理,效率可想而知。这一节,给大家带来一个实用的技巧,利用集合保存批处理的数据,再利用批处理SQL一次性完成,这样可以很大程度地提高我们程序地执行效率。
下面我们直接使用代码来说明:
批量插入
先在goods.xml中新增加一个insert标签
<insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true" keyColumn="goodId" keyProperty="goodId"> insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id) values <!--foreach子标签专门对parameter传入参数中地list集合进行遍历--> <!--item是循环中集合的一项数据,index索引为第几次循环。separator为循环分割符--> <foreach collection="list" item="item" index="index" separator=","> (#{item.title},#{item.subTitle},#{item.originalCost},#{item.currentPrice},#{item.discount},#{item.isFreeDelivery},#{item.categoryId}) </foreach> </insert>
大多数数据库都有这样的插入语法:
INSERT INTO table(字段,字段,字段,…)
VALUES
(a1,b2,c2,…) ,
(a2,b2,c2,…) ,
····
上面编写批处理时,用的就是这个语法。
然后在测试类中编写测试方法:
@Test public void testBatchInsert(){ SqlSession session=null; try { long startTime=new Date().getTime(); session=MybatisUtils.openSession(); List list=new ArrayList(); for (int i = 0; i < 10000; i++) { Goods goods=new Goods(); goods.setTitle("测试商品"); goods.setSubTitle("测试商品子标题"); goods.setOriginalCost(200f); goods.setCurrentPrice(100f); goods.setDiscount(0.5f); goods.setIsFreeDelivery(1); goods.setCategoryId(43); list.add(goods); } session.insert("goods.batchInsert",list); session.commit(); long endTime=new Date().getTime(); System.out.println("执行时间:"+(endTime-startTime)+"毫秒"); }catch (Exception e){ if (session!=null){ session.rollback(); } e.printStackTrace(); }finally { MybatisUtils.closeSession(session); } }
上面的批量插入数据生成的SQL太长了,可能会被服务器拒绝。假设公司有一千万条数据需要导入,那上面的写法肯定是不行的。我们可以采用分段的方式,把一千万条数据,拆分成一千组一万条数据来执行。数据分段,可以同过for循环二次嵌套来完成。 很简单,这里不做演示了。
批量删除
思路:
我们之前写的删除语句是delete from t_goods where goods_id = #{value}; 删除一个具体的数据。
现在要执行批量删除的话,需要对语句进行更改,然后同样要使用foreach:
<delete id="batchDelete" parameterType="java.util.List"> delete from t_goods where goods_id in <!--open设置以什么开始,close设置以什么结束--> <foreach collection="list" item="item" index="index" open="(" close=")" separator=","> #{item} </foreach> </delete>
编写一个测试方法进行测试:
@Test public void testBatchDelete(){ SqlSession session=null; try { long startTime=new Date().getTime(); session=MybatisUtils.openSession(); List list=new ArrayList(); for (int i = 1929; i < 11929; i++) { list.add(i); } session.delete("goods.batchDelete",list); session.commit(); }catch (Exception e){ if (session!=null){ session.rollback(); } e.printStackTrace(); }finally { MybatisUtils.closeSession(session); } }
8.Mybatis注解开发
设计初期的 MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,映射语句也是定义在 XML 中的。而在 MyBatis 3 中,提供了其它的配置方式。MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。它是 XML 和注解配置的基础。注解提供了一种简单且低成本的方式来实现简单的映射语句。虽然注解方便,但是Java 注解的表达能力和灵活性十分有限,很多强大的映射并不能用注解来构建。而且对于复杂的逻辑,注解就无法完成了。注解也不太利于线上维护。所以注解缺点其实是挺多的。
下面列举了Mybatis常用的注解:
这里只是部分注解,如果想了解更多,请参考:https://mybatis.org/mybatis-3/zh/java-api.html
下面来写一个程序,来简单地演示:
实现我们要说明,如果使用注解,我们是不需要使用Mapper的xml文件的,取而代之,我们要创建名为Dao的包,在包中创建一系列的接口。比如GoodsDao,我们利用这些接口加上注解来替代原本的xml文件。
package org.haiexijun.dao; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.haiexijun.entity.Goods; import java.util.List; public interface GoodsDAO { @Select("select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit}") public List<Goods> selectByPriceRange(@Param("min") Float min,@Param("max") Float max, @Param("limit") Integer limit); }
然后在mybatis-config.xml中把dao到入mappers中:
有两种写法
<mappers> <mapper class="org.haiexijun.dao.GoodsDAO"/> </mappers>
或者:
<mappers> <package name="org.haiexijun.dao"/> </mappers>
这两种写法都行,二选一即可。package更实用,因为当dao包下有非常多的dao的时候,如果使用第一种方式导入的话,要写非常多的代码,太麻烦了。直接导入整个dao包不香吗?
到这里,就可以利用这个接口来完成数据查询的操作了。
下面编写测试方法:
@Test public void testAnnotationSelect(){ SqlSession sqlSession=null; try { sqlSession=MybatisUtils.openSession(); GoodsDAO goodsDAO= sqlSession.getMapper(GoodsDAO.class); List<Goods> list=goodsDAO.selectByPriceRange(100f,500f,20); System.out.println(list.size()); }catch (Exception e){ e.printStackTrace(); }finally { MybatisUtils.closeSession(sqlSession); } }
上面是用注解来实现查询,下面再举一个例子来实现一下新增数据
和之前一样,在GoodsDao接口里面定义方法
package org.haiexijun.dao; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.SelectKey; import org.haiexijun.entity.Goods; import java.util.List; public interface GoodsDAO { @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()",before = false,keyProperty = "goodId",resultType =Integer.class) public int insert(Goods goods); }
然后编写测试类来对他进行测试:
@Test public void testAnnotationInsert(){ SqlSession sqlSession=null; try { sqlSession=MybatisUtils.openSession(); Goods goods=new Goods(); goods.setTitle("测试商品"); goods.setSubTitle("测试商品的子标题"); goods.setOriginalCost(200f); goods.setCurrentPrice(100f); goods.setDiscount(0.5f); goods.setIsFreeDelivery(1); goods.setCategoryId(43); GoodsDAO goodsDAO=sqlSession.getMapper(GoodsDAO.class); goodsDAO.insert(goods); sqlSession.commit(); }catch (Exception e){ if (sqlSession!=null){ sqlSession.rollback(); } e.printStackTrace(); }finally { MybatisUtils.closeSession(sqlSession); } }
对于其他的用法,需要用时,再去学习就好。
四.完结感言
敲了一个多礼拜,终于把这篇博客给敲出来了。真的很不容易呀!这篇博客到这里已经将近5万字了,真的佩服自己的毅力。但是我知道,学无止境,我肯定还有很多知识点没有概括到。路过的大佬,如果我有什么重要的知识点遗漏了,希望能在评论区告诉我,感谢你。如果发现有错别字,也欢迎指出。这篇博客的案例代码我放在GitHub上了案例代码。
感谢大家的点赞和关注!真心希望这篇博客对大家的Mybatis学习能有所帮助。
再见!