(2)-MyBatis详解(动态代理,XMl配置,重要标签、映射,动态SQL,高级映射)

上篇文章我们说了MyBatis基础-----入门篇,讲解了MyBatis框架入门。CRUD案例以及基础用法等内容。这篇我们继续进行学习,MyBatis进阶----进阶篇。

回忆一下使用MyBatis的步骤:

  1. 在pom.xml中引入相关jar包
  2. 创建全局配置文件,包含数据库参数
  3. 创建实体类,数据库对应表
  4. 编写对应mapper文件
  5. 加载核心配置文件mybatis-config.xml
  6. 创建SqlSessionFactoryBuilder对象,通过.build方法创建会话工厂,读取流
  7. sqlSessionFactory.openSession创建sqlSession对象,执行sql语句。

(5-7步骤:
Resources.getResourceAsStream(“mybatis-config.xml”);加载核心配置文件得到InputStream------>

通过SqlSessionFactoryBuilder------>创建sqlSessionFactoryBuilder对象------>

通过.build()读取InputStream创建sqlsessionFactory会话工厂------->

sqlSessionFactory.openSession();创建sqlSession对象------>调用对应的方法执行sql语句。)

 private SqlSessionFactory sqlSessionFactory;
    @Before
    public void before() throws IOException {
        //加载核心配置文件mybatis-config.xml
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //创建sqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //读取配置文件的流,创建sqlSessionFactory会话工厂
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }

在上一次学习中,我们发现在使用mybatis通过session调用方法进行增删改查时,会因为sql标识符是字符串类型,且参数是object类型,例如在调用select方法时
在这里插入图片描述
这是sql的调用方无法确定sql标识符是否正确,也无法确定应该传递什么样的参数,从而影响了开发效率,易出错。
MyBatis如何解决?引出今天要写的内容

Mapper代理

直接利用session+id来执行sql的方式存在一些问题

  1. session执行sql时都需要提供要执行sql的id,而这个id是字符串类型,意味着id是否正确在编译期间是无法获知的,必须等到运行时才能发现错误,
  2. sql需要的参数和返回值类都不明确,这也增加了出错的概率
    理想的状况:像调用方法一样调用sql,既避免了直接写id的问题,也可以明确指定方法的参数类型和返回值类型

解决方案:

MyBatis通过动态代理来解决, 动态代理的意思就是自动产生一个实现类。
简单的说动态代理(动态生成),就是在运行过程中自动产生一个对象,用它来代理原本已经存在的对象的方法。我们获得的这个mapper就是一个代理对象

MyBatis中本来由Executor(被代理对象)来完成sql的执行,现在由代理对象(自动生成)来代理Executor完成,代理对象会将我们的操作转交给Executor,由mapper代理去与session进行交互,同时屏蔽了容易出错的地方。

问题是:MyBatis怎么知道代理对象是什么样的对象呢?

这就需要为MyBatis提供Mapper接口,这个接口就是对mapper.xml中的sql语句的声明,与DAO层的接口一样,通过接口,我们可以明确的告诉sql的调用者,返回值类型,方法名称,参数分别是什么。

MyBatis其实就是获取执行的方法名称作为id来查找sql的,所以方法名和id必须完全一致

使用步骤

  1. 创建接口类
package com.cx.mapper;

import com.cx.pojo.Product;

import java.util.List;
public interface ProductMapper {
    //根据id查询
    Product selectById(int pid);
    //根据name实现模糊查询
    List<Product> selectByName(String pname);
}

  1. 提供响应的sql映射
<?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="com.cx.mapper.ProductMapper">
    <!--根据id进行查询-->
    <select id="selectById" parameterType="int" resultType="com.cx.pojo.Product">
        select * from product where pid = #{pid}
    </select>
    <!--根据name进行模糊查询-->
    <select id="selectByName" parameterType="String" resultType="com.cx.pojo.Product">
        select * from product where pname like '%${新疆}%'
    </select>
</mapper>
  1. 获取代理对象 执行方法完成操作
import com.cx.mapper.ProductMapper;
import com.cx.pojo.Product;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;


public class MyTest2 {
    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void before() throws IOException {
        //加载核心配置文件mybatis-config.xml
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //创建sqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //读取配置文件的流,创建sqlSessionFactory会话工厂
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }
    @Test
    public void test1(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
        Product product = mapper.selectById(4);
        System.out.println(product);
        sqlSession.close();
    }
    @Test
    public void test2(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
        List<Product> product = mapper.selectByName("新疆");
        System.out.println(product);
        sqlSession.close();
    }
}

使用mapper代理注意事项

  1. 必须保证mapper.xml中的namespace与接口的全限定名称一致
  2. 方法的名称必须与对应的sql statement的id一致
  3. 方法的参数必须与对应的sql statement的parameterType一致
  4. 方法的返回值必须与对应的sql statement的resultType一致
    案例总结如下:

mapper代理实现步骤:

  1. 创建接口,接口中的方法与sql对应
  2. mapper文件中的namespace指定对应的接口类
  3. 通过sqlsession.getMapper()来获取代理对象

XML配置

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

>configuration(配置)
  >>properties(属性)
  >>settings(设置)
  >>typeAliases(类型别名)
  >>typeHandlers(类型处理器)
  >>objectFactory(对象工厂)
  >>plugins(插件)
  >>environments(环境配置)
    >>>environment(环境变量)
       >>>transactionManager(事务管理器)
       >>>dataSource(数据源)
  >>databaseIdProvider(数据库厂商标识)
  >>mappers(映射器)

注意:>代表层次结构,我们在书写的时候一档要遵循上面的结构偶进行,否则就会报错。
单独使用MyBatis的场景是很少的,后续都会将其与Spring进行整合,并由Spring来对MyBatis进行配置,下面讲解的为需要重点关注,其余的了解即可,若遇到特殊需求可查阅官方文档

属性(properties)

properties可从配置文件或是properties标签中读取需要的参数,使得配置文件各个部分更加独立

内部properties标签

<?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>
 <!--内部标签定义的属性-->
    <properties>
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///sms?serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="***"/>
    </properties>
    <environments default="development">
        <!--        可配置多个环境  并指定默认使用的环境-->
        <environment id="development">
            <!--            指定事务管理器-->
            <transactionManager type="JDBC"/>
            <!--            指定数据源 就是数据来自哪里 这里默认使用MyBatis自带的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <!--                //表示本机 localhost  &amp;就是&  xml中&需要转译-->
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--    指定要加载的映射文件-->
    <mappers>
        <mapper resource="mapper/ProductMapper.xml"/>
    </mappers>
</configuration>

为了更清晰的表达,标出变化之处:
在这里插入图片描述

外部配置文件

对于我们之前mybatis的mybatis-config.xml的文件,我们直接把jdbc相关参数配置到xml文件中去了,但是实际开发过程中数据量大,就会比较麻烦,我们可以建立一个单独的文件,进行存储,使用属性标签从文件中取值,此时就用到了外部配置文件

jdbc.properties位于resource下
在resource下建立一个jdbc.properties

driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql:///sms?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username = root
password = ***

mybatis-config.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>
    <!--外部标签关联属性文件-->
    <properties resource="jdbc.properties" />
    <environments default="development">
        <!--        可配置多个环境  并指定默认使用的环境-->
        <environment id="development">
            <!--            指定事务管理器-->
            <transactionManager type="JDBC"/>
            <!--            指定数据源 就是数据来自哪里 这里默认使用MyBatis自带的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <!--                //表示本机 localhost  &amp;就是&  xml中&需要转译-->
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--    指定要加载的映射文件-->
    <mappers>
        <mapper resource="mapper/ProductMapper.xml"/>
    </mappers>
</configuration>

在这里插入图片描述
当内部和外部属性出现同名时,则优先使用外部的;

别名(typeAliases)

typeAliases用于为Java的类型取别名,从而简化mapper中类名的书写
比如在mapper.xml中的resultType,要写包名加类名,书写不够简便。
注意typeAliases标签位于properties标签之下,书写顺序不能颠倒

为某个类定义别名

  <!--为某个类定义别名-->
    <typeAliases>
        <!--type时全类名,alias取得时别名,可以省略,省略后默认的是类名,首字母不区分大小写-->
        <typeAlias type="com.cx.pojo.Product" alias="prodcct" />
    </typeAliases>

在这里插入图片描述
那么问题又来了,显示开发中,实体类会很多,这样会比较麻烦,我们可以直接扫描包,批量定义别名:

<!--    批量定义别名-->
    <typeAliases>
        <!--扫描包,都设置为类名,首字母不区分大小写-->
        <package name="com.cx.pojo"/>
    </typeAliases>

上面两种定义别名的方式,用法一样,如下:

<?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="com.cx.mapper.ProductMapper">
    <!--根据id进行查询-->
    <select id="selectById" parameterType="int" resultType="Product">
        select * from product where pid = #{pid}
    </select>
    <!--根据name进行模糊查询-->
    <select id="selectByName" parameterType="String" resultType="product">
        select * from product where pname like '%${新疆}%'
    </select>
</mapper>

在这里插入图片描述
可以发现,resultType输出映射里面product实体类首字母大小写都可以。

别名冲突

使用package批量设置时很容易出现别名冲突,这是就需要使用@Alias注解来为冲突的类单独设置别名

@Alias("products1")
public class Product {
    private int pid;
    private String pname;
    private float price;
    private Date pdate;
    private String cid;
.....}

注意要扫描的是这个类的包,否则注解是无效的

下面mybatis已经存在的别名:

_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
bytebyte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

映射(Mappers)

在mapper.xml文件中定义sql语句后,就必须让MyBatis知道,到哪里去找这些定义好的sql,这就需要在配置文件中指出要加载的mapper的位置;MyBatis支持4种方式来加载mapper文件
以下有四种方式:

  1. 直接指定mapper文件
    resource
    指定资源文件的相对目录 相对于classpath maven项目会自动将java和resources都添加到classpath中
    所以相对与resources来指定路径即可
    <mappers>
        <mapper resource="mapper/ProductMapper.xml"/>
    </mappers>
  1. 指定文件的绝对路径,MyBatis支持但是一般不用
    url
<mappers>
	 <mapper url="file:///Users/jerry/Downloads/MYB/src/main/resources/mapper/ProductsMapper.xml"/>
</mappers>
  1. 通过指定接口 让MyBatis自动去查找对应的Mapper文件
    class
    这种方式要求映射文件和接口处于同一目录下,并且名称相同
    要保证上述要求只需要在resources下创建于接口包名相同的目录即可
    <mappers>
        <mapper class="com.cx.mapper.ProductMapper"/>
    </mappers>

注意:运行前先clean,否则可能因为之前已经存在target而不会重新编译,导致无法加载新建的mapper文件

4.指定包名称,扫描包下所有接口和映射文件
这种方式同样要求映射文件和接口处于同一目录下,并且名称相同

<mappers>
   <package name="com.cx.mapper"/>
</mappers>

注意:
上面方法3和方法4,

对于普通项目:

我们可以把mapper.xml 和 接口 放在一个目录下

但是对于maven项目:

我们可以把接口文件放在java下,把对应的mapper.xml放在recources下;
1.如果是单级目录,只需保证包名一致:
即在Java和recources下创建同名的文件夹mapper;
2.如果是多级目录此时注意如果要保持目录一致:
即在java我们可以创建时输入 一级目录.二级目录.三级目录 等;
但是在recources下不可以这样创建,应该使用“/” 即:一级目录/二级目录/三级目录 

在这里插入图片描述

此处是因为在recources下会把“.”当成文件名的一部分,创建一个包;
而Java会当成分别包的符号,创建多级目录,虽然看着一样,但是结构不同。

动态SQL

动态SQL指的是SQL语句不是固定死的,可以根据某些条件而发生相应的变化,这样的需求非常多见

例如:页面提供的搜索功能,要根据用户给出的一个或多个条件进行查询,查询条件不固定。

在JDBC中,我们通过Java代码来判断某个条件是否有效然后拼接SQL语句,这是非常繁琐的,MyBatis的动态SQL很好的解决了这个问题,使判断逻辑变得简洁直观;

在Mapper中通过标签来完成动态SQL的生成

1. if

从页面接受参数后我们会将参数打包为对象,然后将对象作为参数传给MyBatis执行查询操作,sql语句需要根据是否存在参数而动态的生成

接口声明方法:

 //按条件查询
    List<Product> selectByProduct(Product product);

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="com.cx.mapper.ProductMapper">
<!--if标签,按条件查询-->
    <select id="selectByProduct" parameterType="product" resultType="product">
        select * from product where 1=1 
        <if test="pid != null and pid != 0">
            and pid = #{pid}
        </if>
        <if test="pname != '' and pname != null">
            and pname like "%${pname}%"
        </if>
    </select>
</mapper>

where 1=1用于保持sql的正确性,当不存在条件时,则查询全部

测试:

 private SqlSessionFactory sqlSessionFactory;
    @Before
    public void before() throws IOException {
        //加载核心配置文件mybatis-config.xml
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //创建sqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //读取配置文件的流,创建sqlSessionFactory会话工厂
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }
     @Test
    public void selectByProduct(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
        Product product = new Product();
        //按id查询
       // product.setPid(2);
        //根据名字模糊查询
        product.setPname("葡萄");
        List<Product> products = mapper.selectByProduct(product);
        System.out.println(products);
        sqlSession.close();
    }

2.where

where的作用就是用于取出上面的where 1=1,因为这会让人看起来产生疑惑,其作用是将内部语句中的第一个and去除

<?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="com.cx.mapper.ProductMapper">
<!--if标签,按条件查询,加入where标签-->
    <select id="selectByProduct" parameterType="product" resultType="product">
        select * from product
        <where>
            <if test="pid != null and pid != 0">
                and pid = #{pid}
            </if>
            <if test="pname != '' and pname != null">
                and pname like "%${pname}%"
            </if>
        </where>
    </select>
</mapper>

3. foreach

当一个条件中需要多个参数时则需要将多个参数拼接到一起,例如: in, not in

接口声明方法:

//方式一:参数是list类型
List<Product> selectByIds(List<Integer> ids);
//方式二:参数是product类型
List<Product> selectByidss(Product product);

mapper:

<!--foreach标签,parameterType 是list  按ids进行查询-->
    <select id="selectByIds" parameterType="list" resultType="product">
        select * from product
        <where>
            <if test="list != null">
                <foreach collection="list" open="and pid in(" close=")" separator="," item="id">
                    #{id}
                </foreach>
            </if>
        </where>
    </select>
<!--方式二  参数类型是product类型-->
    <select id="selectByidss" parameterType="product" resultType="product">
        select * from product
        <where>
            <if test="ids != null">
                <foreach collection="ids" open="and pid in (" close=")" separator="," item="id" >
                    #{id}
                </foreach>
            </if>
        </where>
    </select>

字符串:

<if test="typeList!=null and typeList.size > 0">
        and
        <foreach collection="typeList" item="type" index="index" open="(" close=")" separator="OR">
            D.CODE like concat(concat('%', #{type}), '%')
        </foreach>
    </if>
<if test="ids != null"> 这里不仅判断属性是否为空还判断集合中是否有元素

foreache 标签属性说明:
强调:动态sql本质就是在拼接字符串,带着自己拼接sql的思路来编写动态sql会更好理解
  collection	要遍历的集合
  open				拼接的前缀
  close				拼接的后缀
  separator		拼接元素之间的分隔符
  item				遍历得到的临时变量名
  index				当前元素的索引(不常用)
注意方式二,参数类型是product类型,需要为POJO(Products)对象增加ids属性,并提供getset方法
private List<Integer> ids;

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }

书写测试类

//方式一测试方法
    @Test
    public void selectByIds(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
        List<Integer> ids = new ArrayList<>();
        ids.add(2);
        ids.add(3);
        ids.add(4);
        System.out.println(ids);
        List<Product> products = mapper.selectByIds(ids);
        System.out.println(products);
        sqlSession.close();
    }
 //方式二测试方法
    @Test
    public void selectByidss(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
        List<Integer> ids = new ArrayList<>();
        ids.add(2);
        ids.add(3);
        ids.add(4);
        Product product = new Product();
        product.setIds(ids);
        List<Product> products = mapper.selectByidss(product);
        System.out.println(products);
        sqlSession.close();
    }

4. set

set标签用于更新语句,当同时要更新多个字段时,我们需要留意当前是否是最后一个set,避免在后面出现,符号,使用set标签后可自动去除最后的逗号

mapper:

<update id="updateProductTest" parameterType="products">
    update products
    <set>
        <if test="pname != null and pname != ''">
            pname = #{pname},
        </if>
        <if test="price != null and price > 0">
            price = #{price},
        </if>
        <if test="pdate != null">
            pdate = #{pdate},
        </if>
        <if test="cid != null and cid != ''">
            cid = #{cid},
        </if>
    </set>
    where pid = #{pid}
</update>

测试代码:

private SqlSessionFactory sqlSessionFactory;
    @Before
    public void before() throws IOException {
        //加载核心配置文件mybatis-config.xml
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //创建sqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //读取配置文件的流,创建sqlSessionFactory会话工厂
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }
 //set测试
    @Test
    public void updateById(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
        Product product = mapper.selectById(2);
        //Product product = new Product();
        product.setPname("车厘子");
        product.setPrice(1000.0);
        mapper.updateById(product);
        sqlSession.close();
    }

5. sql与include

Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。 例如可以吧sql语句中的字段列表提取出来作为通用的sql片段。然后在sql语句中使用 include 节点引用这个sql片段。

<!--提取片段-->
<sql id="fields">pid,pname,price,cid</sql>

<select id="includeTest" resultType="products">
    select
    <include refid="fields"/> <!-- 引用片段-->
    from products
</select>

高级映射

在一些情况下数据库的记录和POJO对象无法直接映射,包括两种情形:

  • 数据库字段与POJO字段名称不同(可以避免);

  • 关联查询时,需要将关联表的数据映射为另一个类型的POJO(一对一),或List中(一对多);

在MyBatis中通过resultMap来完成自定义映射

1.自定义字段与属性映射

当数据库列名自定义的映射对象的字段名不一样时,就可以使用resultMap来自定义映射关系。

举个例子,orders表中数据库表为:
在这里插入图片描述
实体类为:
在这里插入图片描述
此时如果按上面那样直接返回类型写成Order类,因为字段不匹配,所以name会映射不上,结果就会输出“null”,这时候就可以按下面使用resultMap:
接口:

Order selectById(int id);

mpper:

<?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="com.cx.mapper.OrderMapper">
    <resultMap id="order_map" type="order" autoMapping="true">
        <id column="id" property="id" />
        <result column="numbers" property="number" />
    </resultMap>
    <select id="selectById" parameterType="int" resultMap="order_map">
        select * from orders where id = #{id}
    </select>
</mapper>

对于自定义字段与属性映射,需要知道的是

  • select 标签里的 resultMap的值对应于resultMap标签的id值
  • resultMap 标签的type对应返回值类型
  • autoMapping=“true” 自动映射,如果列名与字段名一致,就会自动映射,不一致可以通过id标签与result标签进行映射 ,如果不开启就算是名字完全一致,也需要自己一个一个写,麻烦
  • id标签:主键定义映射标签,注意要放在result上面,colum 数据库对应列名,property对应实体类字段名
  • result 普通属性定义标签,如果有id标签,放在id标签后面,colum 数据库对应列名 property对应的对象字段名

2. 关联查询

2.1 关联关系

两个表之间记录的对应关系,分为一对一和一对多。

而多对多则是三张表之间的关系,若掌握了两张表之间的一对多关系的处理,则多对多也就不是问题了,因为本质上多对多就是两个一对多组成的
在这里插入图片描述在这里插入图片描述
这两张表之间存在一对一和一对多

站在user表角度来看 一个用户可能对应多个订单即一对多

站在orders表角度来看 一个订单只能对应一个用户即一对一

一对一查询

先来个案例:
需求:根据订单Id查询订单信息以及用户信息
Order类里面包含一个User类,我们要做的就是把对应的列名映射到User类中。把用户信息当成一个属性
书写pojo类
order类

public class Order {
    private int id,user_id;
    private String number;
    private Date createtime;
    private User user;//关联的类
    }

user类

public class User {
    private int id;
    private String name;
    private Date birthday;
    private String sex,address;
}

当然不要忘了get/set方法噢

书写接口

Order selectAllById(int id);

书写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="com.cx.mapper.OrderMapper">
    <!--一对一映射-->
    <resultMap id="order_map2" type="order" autoMapping="true">
        <id property="id" column="oid"/>
        <result property="number" column="numbers" />
        <association property="user" javaType="user" autoMapping="true">
            <id property="id" column="uid" />
            <result property="name" column="username"/>
        </association>
    </resultMap>
    <select id="selectAllById" parameterType="int" resultMap="order_map2">
        select * ,orders.id oid,user.id uid from orders inner join user
        on orders.user_id = user.id
        where orders.id = #{id}
    </select>
</mapper>

书写测试类:

@Test
    public void test2(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
        Order order = mapper.selectAllById(2);
        System.out.println(order);
    }
  • association 一对一映射,映射到类中去
  • property 对应字段名
  • javaType映射类型
  • autoMapping=“true” 自动映射开启,如果不开启就算是名字完全一致,也需要自己一个一个写,麻烦
  • 上面用法和普通映射一样,为了防止两个表的id名重复,所以在查询时取了别名,查询时采用的是内连接。

当连接查询出现重复字段时如:两个表都有id字段,MyBatis简单的取第一个匹配的字段值,很多时候这是不正确的,我们可以给这个重复的字段取个别名来避免,像这样:
在这里插入图片描述

一对多映射:

需求:根据用户id查询用户信息与用户订单信息
书写pojo
order:

public class Order {
    private int id,user_id;
    private String number;
    private Date createtime;
    }

user:

public class User {
    private int id;
    private String name;
    private Date birthday;
    private String sex,address;
    private List<Order> orders;//产品类
}

接口:

package com.cx.mapper;

import com.cx.pojo.User;

public interface UserMapper {
    //根据用户id查询用户信息,以及订单信息,一对多
    User selectAllByUserId(int id);
}

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="com.cx.mapper.UserMapper">
    <resultMap id="user_map" type="user" autoMapping="true">
        <id property="id" column="uid" />
        <result property="name" column="username" />
        <collection property="orders" javaType="list" ofType="order" autoMapping="true">
            <id property="id" column="oid" />
            <result property="number" column="numbers" />
        </collection>
    </resultMap>
    <!--一对多映射-->
    <select id="selectAllByUserId" parameterType="int" resultMap="user_map">
        select *,user.id uid,orders.id oid
        from user join orders
        on user.id = orders.user_id
        where user.id = #{id}
    </select>
</mapper>

测试类

@Test
    public void selectAllByUserId(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectAllByUserId(1);
        System.out.println(user);
    }

autoMapping=“true” 将自动映射字段名与属性名能对应的字段,我们只需要添加对应不上的即

注意:无论如何在collection标签中必须至少存在一个手动映射字段
否则,将不会合并重复的主记录(user) 按照官方的说法,建议将手动映射id字段,可提高整体性能

另外collection标签中
collection 映射一个集合
property 映射的集合名
ofType用于指定元素的类型
javaType指定容器类型
在这里插入图片描述
一对多查询时,看resultMap的type,以它来做一个分组

嵌套映射,select

当关联查询非常复杂时,可以用嵌套的select,其原理是在映射复杂数据时执行另一个select来完成

上面查询可以写成下面形式:

<resultMap id="user_map2" type="user" autoMapping="true">
        <id property="id" column="id" />
        <result property="name" column="username" />
        <!-- 
			property指定数据要放在哪个属性中
			javaType容器类型与属性对应
			ofType元素类型
			column要传给子查询的参数
			select子查询的id
		-->
        <collection property="orders" javaType="list" ofType="order" column="id" select="selectOrderByUserid"/>
    </resultMap>
    <!-- 外层查询-->
    <select id="selectAllByUserId2" parameterType="int" resultMap="user_map2">
        select * from user where id = #{id}
    </select>

    <resultMap id="myorder" type="order" autoMapping="true">
        <result property="number" column="numbers" />
    </resultMap>
    <select id="selectOrderByUserid" parameterType="int" resultMap="myorder">
        select  * from orders where user_id = #{id}
    </select>

上面是一对多映射,当然一对一映射也是可以的,开始的一对一映射可以修改为:

 <resultMap id="order_map3" type="order">
        <id property="id" column="id" />
        <result property="number" column="numbers" />
        <result column="user_id" property="user_id" />
         <!-- 指定嵌套查询 column是传给内层查询的参数 -->
        <association property="user" javaType="user" column="user_id" select="selectByUserId"></association>
    </resultMap>
    <!-- 外层查询-->
    <select id="selectAllById2" parameterType="int" resultMap="order_map3">
        select * from orders where id = #{id}
    </select>

    <resultMap id="myMap" type="user" autoMapping="true">
        <id column="id" property="id" />
        <result property="name" column="username" />
    </resultMap>
    <!-- 嵌套查询-->
    <select id="selectByUserId" parameterType="int" resultMap="myMap">
        select * from user where id = #{user_id}
    </select>

使用select 与不用select的区别:

  • 不用select时对应的sql语句为关联查询,一次性返回所有数据,然后对其进行去重,提取

  • 使用select时,sql语句多为单表查询,使用外层查询的某个字段作为内层查询的参数,是两条单独的sql语句

下一篇:(3)-mybatis之注解

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值