关于spring:长文Spring学习笔记七Mybatis映射器动态SQL

6次阅读

共计 20507 个字符,预计需要花费 52 分钟才能阅读完成。

1 概述

本文次要讲述了如何应用 MyBatis 中的映射器以及动静 SQL 的配置。

2 MyBatis配置文件概览

MyBatis配置文件次要属性如下:

  • <settings>:相干设置,键值对模式
  • <typeAliases>:类型别名
  • <typeHandlers>:类型处理器
  • <objectFactory>:对象工厂
  • <plugins>:插件,蕴含若干个<plugin>
  • <environments>:环境配置,蕴含若干个 <environment>,在<environment> 中能够指定事务管理器 <transactionManager> 以及数据源<dataSource>
  • <databaseIdProvider>:数据库厂商标识
  • <mappers>:映射器,蕴含若干个<mapper>

留神程序不能颠倒,否则启动时会产生异样。

3 筹备步骤

因为本文大部分的代码都只给出了要害的语句而没有残缺的工程,因而如果想要实现一遍请 clone 此处的代码(Kotlinclone 此处),并:

  • 利用 resources/sql 下的脚本文件创建数据库以及数据表,并插入相应数据
  • 批改 MyBatisSpringdhcp2 等依赖为最新版本并批改 MySQL 驱动为对应版本
  • 批改 applicationContext.xml 文件中的数据库用户名,明码以及数据库URL,可能须要批改驱动
  • 开启数据库服务并进行测试,运行 MainTest 中的测试方法即可,失常来说会呈现如下后果:

4 映射器概述

MyBatis的映射器由一个接口加上 XML 映射文件组成,是最简单的组件,映射文件罕用元素如下:

  • <select>:查问语句
  • <insert>/<update>/<delete>插入 / 更新 / 删除 语句,返回操作所影响的行数,比方插入了两行,操作胜利了影响的行数则为两行,返回整数 2
  • <sql>:自定义的SQL
  • <resultMap>:提供映射规定

上面先来看一下最罕用的<select>

4.1 <select>

示例(在 mapper/UserDao.xml 间接增加即可):

<select id="selectById" parameterType="Integer" resultType="pers.init.entity.User">
    select * from user where id = #{id}
</select>

其中 id 是惟一标识符,承受一个 Integer,返回com.pojo.User 对象,后果集主动映射到 com.pojo.User 中。

罕用属性如下:

  • id<select>语句的全局惟一标识符
  • paramterType:示意传入 SQL 语句的参数类型的全限定名或别名,可选,能主动推断
  • resultType:执行 SQL 后返回的类型
  • resultMap:与 resultType 相似,resultType默认一一对应映射,比方表字段名为 id,则映射到实体类的id 中,而 resultMap 须要手动定义映射关系,这样就能够把表字段中的 id 映射到实体类的 id1,或id2,或id3resultTyperesultMap两者须要指定一个,不能同时存在
  • flushCache:设置调用 SQL 后是否要求 MyBatis 清空之前查问的本地缓存以及二级缓存,默认false
  • useCache:启动二级缓存,默认true
  • timeout:超时参数,单位秒
  • fetchSize:获取记录的总条数设定
  • statementType:应用哪个 JDBCStatement,取值能够为STATEMENT/PREPARED/CALLABLE,别离示意Statement/PreparedStatement/CallableStatement
  • resultSetType:针对 JDBCResultSet,可设置为 FORWARD_ONLY/SCROLL_SENSITIVE/SCROLL_INSENSITIVE,别离示意 只容许向前拜访 / 双向滚动,不及时更新 / 双向滚动,及时更新

并批改 UserDao,增加一个selectById 办法:

User selectById(Integer id);

能够间接测试了:

@Test
public void selectById()
{System.out.println(dao.selectById(1));
}

上面来看一下如何传递多个参数。

4.2 传递参数

有了最根本的 select 后,传递 id 这种繁多参数很容易,然而理论状况中很多时候须要传递多个参数,MyBatis中传递多个参数有两种形式:

  • 通过 Map 传递
  • 通过 JavaBean 传递

4.2.1 Map

能够应用 Map 传递多个参数,示例 <select> 如下:

<select id="selectByMap" resultType="pers.init.entity.User" parameterType="map">
    select * from user where name like concat('%', #{name}, '%') and age = #{age}
</select>

参数名 name 以及 ageMap的键。

接着在 UserDao 下增加:

User selectByMap(Map<String,String> map);

而后在主类中应用 Map 增加键值对:

@Test
public void selectByMap()
{Map<String,String> map = new HashMap<>();
    map.put("name","111");
    map.put("age","33");
    System.out.println(dao.selectByMap(map));
}

这样就能传递多个参数进行查问了。

4.1.2 应用JavaBean

传递多个参数的另一种办法是利用 JavaBean 传递,创立一个 POJO 类:

@Getter
@Setter
@Builder
@ToString
public class UserPOJO {
    private String name;
    private Integer age;
}

批改 UserDao 接口办法:

public User selectByPOJO(UserPOJO user)

接着批改映射文件,实际上批改 parameterType 即可:

<select id="selectByPOJO" resultType="pers.init.entity.User" parameterType="pers.init.pojo.UserPOJO">
    select * from user where name like concat('%', #{name}, '%') and age = #{age}
</select>

留神拜访传递的参数时间接应用 POJO 类的属性名即可,毋庸加上相似 UserPOJO. 的前缀。

最初进行测试:

@Test
public void selectByPOJO()
{UserPOJO pojo = UserPOJO.builder().age(33).name("111").build();
    System.out.println(dao.selectByPOJO(pojo));
}

4.2 <insert>

<insert>用于插入,大部分属性与 <select> 雷同,上面是几个特有属性:

  • keyProperty:将插入操作的返回值赋给 POJO 类的某个属性
  • keyColumn:用于设置主键列的地位,当表中第 1 列不是主键时须要设置该参数,联结主键能够应用逗号分隔
  • useGeneratedKeys:应用 JDBCgetGeneratedKeys获取数据库外部产生的主键,默认false

比方典型的主键回填 <insert> 如下:

<insert id="insertUser1" parameterType="pers.init.entity.User" keyProperty="id" useGeneratedKeys="true">
    insert into user(name, age) values (#{name}, #{id})
</insert>

这样就会利用数据库生成的自增主键回填到 Userid属性中,UserDao接口如下:

int insertUser1(User user);

一般来说插入操作返回一个整数,示意操作影响的行数,因而能够设置返回值为int,测试如下:

@Test
public void insertUser1()
{User user = User.builder().age((short) 88).name("test1").build();
    System.out.println(dao.insertUser1(user));
    System.out.println(user.getId());
}

另外如果不反对自增主键,能够应用 selectKey 自定义生成主键,比方:

<insert id="insertUser2" parameterType="pers.init.entity.User">
    <selectKey keyProperty="id" resultType="integer" order="BEFORE">
        select if(max(id) is null,1,max(id)+1) as newId from user
    </selectKey>
    insert into user(id,name,age) values(#{id},#{name},#{age})
</insert>

<selectKey>中的 keyProperty 指定了新主键 newId 返回给 pers.pojo.Userid属性,order设置执行程序,BEFORE/AFTER示意执行 <selectKey> 之后 / 之前再执行插入语句。

测试:

@Test
public void insertUser2()
{User user = User.builder().age((short) 10).name("test2").build();
    System.out.println(dao.insertUser2(user));
    System.out.println(user.getId());
}

4.3 <update>/<delete>

返回一个整数,属性与 <insert>/<select> 相似,简略示例如下:

<update id="updateUser" parameterType="pers.init.entity.User">
    update user set name=#{name}, age=#{age} where id = #{id}
</update>

<delete id="deleteUser" parameterType="Integer">
    delete from user where id = #{id}
</delete>

同理 update/delete 返回一个整数,示意操作影响的行数,因而设置 UserDao 接口如下:

int updateUser(User user);
int deleteUser(Integer id);

测试:

@Test
public void updateUser()
{User user = User.builder().id(3).name("3333333").age((short)11).build();
    selectAll();
    System.out.println(dao.updateUser(user));
    selectAll();}

@Test
public void deleteUser()
{selectAll();
    System.out.println(dao.deleteUser(3));
    selectAll();}

4.4 <sql>

用于定义 SQL 的一部分,以不便前面的 SQL 语句援用,比方:

<sql id="column">
    id,name,age
</sql>
<select id="selectBySqlColumn" resultType="pers.init.entity.User">
    select <include refid="column"/> from user
</select>

UserDao接口:

List<User> selectBySqlColumn();

测试:

@Test
public void selectBySqlColumn()
{System.out.println(dao.selectBySqlColumn());
}

5 <resultMap>

下面进步过,<resultMap><resultType> 要弱小,然而须要手动定义映射关系,一个常见的 <resultMap> 如下:

<resultMap type="package1.package2.package3.POJO" id="resultMapId">
    <constrcutor>                       <!-- 实例化时将后果注入到构造方法中 -->
        <idArg />                       <!--ID 参数 -->
        <arg />                         <!-- 一般参数 -->
    </constrcutor>
    <id />                              <!-- 示意哪个列是主键 -->
    <result />                          <!-- 注入到字段 /JavaBean 属性的一般后果 -->
    <association property="">           <!-- 一对一关联 -->
    <collection property="">            <!-- 一对多关联 -->
    <discriminator javaType="">         <!-- 应用后果值决定哪个后果映射 -->
        <case value="">                 <!-- 基于某些值的后果映射 -->
    </discriminator>
</resultMap>

5.1 应用Map

查问 SQL 的后果能够应用 Map/POJO 存储,应用 Map 存储不须要手动编写<resultMap>,默认表属性名是键值对的键:

<select id="selectReturnMap" resultType="Map">
    select * from user
</select>

可用 List<Map> 来接管返回后果,一条记录映射到一个 Map 对象,Map中的 keyselect的字段名。

示例的 UserDao 办法如下:

List<Map<String,Object>> selectReturnMap();

其中 Map 类型为Map<String,Object>,测试方法如下:

 @Test
public void selectReturnMap()
{dao.selectReturnMap().forEach(System.out::println);
}

5.2 应用POJO

如果应用 POJO 存储返回的对象时,须要先定义一个 POJO 类,能够在下面的 UserPOJO 根底上加上一个 id 属性:

@Getter
@Setter
@Builder
@ToString
public class UserPOJO {
    private Integer id;
    private String name;
    private Integer age;
}

接着编写映射文件:

<resultMap id="testPOJO" type="pers.init.pojo.UserPOJO">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
</resultMap>

其中 property 指定 POJO 的属性,column是表字段名,最初配合 <select> 应用,指定 resultMap 为对应id

<select id="selectReturnPOJO" resultMap="testPOJO">
    select * from user
</select>

返回后果能够用 List<UserPOJO> 接管:

List<UserPOJO> selectReturnPOJO();

测试方法:

@Test
public void selectReturnPOJO()
{dao.selectReturnPOJO().forEach(System.out::println);
}

6 级联查问

级联查问就是利用主键与外键的关系进行组合查问,比方表 A 的一个外键援用了表 B 的一个主键,查问 A 时,通过 A 的外键将 B 的相干记录返回,这就是级联查问。常见的级联查问有三种:

  • 一对一
  • 一对多
  • 多对多

MyBatis反对一对一以及一对多级联,没有对多对多级联提供反对,然而能够用多个一对多级联实现多对多级联。上面别离来看一下。

6.1 一对一

一对一级联查问是最常见的级联查问,能够通过 <resultMap> 中的 <association> 进行配置,通常应用的属性如下:

  • property:映射到实体类的对象属性
  • column:指定表中对应的字段
  • javaType:指定映射到实体对象属性的类型
  • select:指定引入嵌套查问的子 SQL 语句,用于关联映射中的嵌套查问

上面通过一个例子进行阐明,例子分五步:

  • 创立数据表
  • 创立实体类
  • 编写映射文件
  • 批改长久层接口
  • 增加测试方法

6.1.1 数据表

为了不便新增表以及数据都写在一起:

use test;

drop table if exists idcard;
drop table if exists person;

create table idcard(id int(10) primary key auto_increment,
    code char(18) collate utf8mb4_unicode_ci default null
);

create table person(id int(10) primary key,
    name varchar(20) collate utf8mb4_unicode_ci default null,
    age smallint default null,
    idcard_id int(10) default null,
    key idcard_id(idcard_id),
    constraint idcard_id foreign key (idcard_id) references idcard(id)
);

insert into idcard(`code`) values('123456789123456789');

insert into person(`id`,`name`,`age`,`idcard_id`) values (1,'111',22,1);

6.1.2 实体类

@Data
public class IdCard {
    private Integer id;
    private String code;
}

@Data
public class Person {
    private Integer id;
    private String name;
    private Integer age;
    private IdCard card;
}

另外还须要创立一个映射后果的 POJO 类:

@Data
public class PersonPOJO {
    private Integer id;
    private String name;
    private Short age;
    private String code;
}

6.1.3 映射文件

映射文件分为两个:

  • IdCardMapper.xml
  • PersonMapper.xml

首先是 IdCardMapper.xml,加上一个<select> 即可,留神 namespace 的地位填写正确,对应 dao 的地位。

<?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="pers.oneToOne.dao.IdCardDao">
    <select id="selectCodeById" parameterType="Integer" resultType="pers.oneToOne.entity.IdCard">
        select * from idcard where id = #{id}
    </select>
</mapper>

其次是PersonMapper.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="pers.oneToOne.dao.PersonDao">
    <resultMap id="personMap1" type="pers.oneToOne.entity.Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <association property="card" column="idcard_id" javaType="pers.oneToOne.entity.IdCard"
                     select="pers.oneToOne.dao.IdCardDao.selectCodeById"/>
    </resultMap>
    <select id="selectPersonById1" parameterType="Integer" resultMap="personMap1">
        select *
        from person
        where id = #{id}
    </select>

    <resultMap id="personMap2" type="pers.oneToOne.entity.Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <association property="card" javaType="pers.oneToOne.entity.IdCard">
            <id property="id" column="idcard_id"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>

    <select id="selectPersonById2" parameterType="Integer" resultMap="personMap2">
        select p.*, ic.code
        from person p,
             idcard ic
        where p.idcard_id = ic.id
          and p.id = #{id}
    </select>

    <select id="selectPersonById3" parameterType="Integer" resultType="pers.oneToOne.pojo.PersonPOJO">
        select p.*, ic.code
        from person p,
             idcard ic
        where p.idcard_id = ic.id
          and p.id = #{id}
    </select>
</mapper>

首先第一个 <resultMap> 先指定 id 等属性,接着是<association>

  • property是实体类属性,留神类型为IdCard
  • column是表字段名,类型为int(10)
  • javaType是通过前面的 select 返回的类型,能够了解成是 property 的类型,也就是IdCard
  • select指定嵌套查问应用的 SQL,对应于IdCardDao.xml 中的selectCodeById

接着在一个 <select> 中的 resultMap 指定该 mapid即可。应用这种办法执行的是两次SQL

  • 一次是select * from person where id=?
  • 一次是select * from idcard where id=?

最初再把后果整起起来,开启调试能够发现实际上也是执行了两条SQL

而第二个 <resultMap> 中,在 <association> 外面没有了 select 属性,间接将后果映射到 SelectPersonById 中,这是执行一条 SQL 语句的后果:

select p.*,ic.code from person p,idcard ic where p.idcard_id = ic.id and p.id=#{id}

理论查问如下:

如果须要重要能够将其配置成<resultMap>,比方:

<association property="card" resultMap="resultMap" />
<resultMap id="resultMap">
    <id property="id" column="idcard_id"/>
    <result property="code" column="code"/>
</resultMap>

而最初一个 <select> 是进行连贯查问,无需额定的<resultMap>,理论执行状况如下:

6.1.4 Dao接口

这个比较简单:

public interface PersonDao {Person selectPersonById1(Integer id);
    Person selectPersonById2(Integer id);
    PersonPOJO selectPersonById3(Integer id);
}

6.1.5 测试

@Test
public void selectPersonById()
{System.out.println(dao.selectPersonById1(1));
    System.out.println(dao.selectPersonById2(1));
    System.out.println(dao.selectPersonById3(1));
}

留神在测试之前,须要批改配置文件mybatis-config.xml

<configuration>
    <settings>
        <!-- 提早加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 按需加载 -->
        <setting name="aggressiveLazyLoading" value="true"/>
        <!-- 调试开关,打印执行的 SQL-->
<!--        <setting name="logImpl" value="STDOUT_LOGGING"/>-->
    </settings>
    <mappers>
        <!--initMapper-->
        <mapper resource="mapper/init/UserMapper.xml" />
        <mapper resource="mapper/init/TestMapper.xml" />
        <!-- 一对一级联 Mapper-->
        <mapper resource="mapper/oneToOne/PersonMapper.xml" />
        <mapper resource="mapper/oneToOne/IdCardMapper.xml" />
    </mappers>
</configuration>

前两个 <setting> 示意开启提早加载以及按需加载,前面一个是设置调试开关,最初在上面的 <mappers> 加上 <mapper> 对应的 xml 的地位。

要留神的一个是 <settings> 须要写在 <mappers> 的后面。

另外因为 Dao 接口没有加上 @Mapper 注解,因而须要在 applicationContext.xml 中手动加上 Dao 地位:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="pers.oneToOne.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sql.SessionFactory"/>
</bean>

测试后果:

6.2 一对多

一对多的级联查问与一对一解决有相似之处,次要是映射文件中的 <collection> 配置,例子也是和下面一样分五步。

6.2.1 数据表

须要两张表:

  • user
  • order

user能够沿用后面的 user 表,而 order 表如下:

use test;
drop table if exists orders;

create table orders(id int(10) primary key auto_increment,
    ordersn varchar(10) collate utf8mb4_unicode_ci default null,
    user_id int(10) default null,
    key user_id(user_id),
    constraint user_id foreign key (user_id) references user(id)
);

insert into orders(`ordersn`,`user_id`) values ('testorder1',1),('testorder2',1),('testorder3',1);

6.2.2 实体类

增加实体类Orders

@Data
public class Orders {
    private Integer id;
    private String ordersn;
}

同时创立一个带 OrdersUser

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserWithOrders {
    private Integer id;
    private String name;
    private Short age;
    private List<Orders> ordersList;
}

6.2.3 映射文件

两个:

  • OrdersMapper.xml
  • UserWithOrdersMapper.xml

首先是OrdersMapper.xml,只有一个简略的<select>

<?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="pers.oneToMany.dao.OrdersDao">
    <select id="selectOrdersById" parameterType="Integer" resultType="pers.oneToMany.entity.Orders">
        select * from orders where user_id=#{id}
    </select>
</mapper>

接着是UserWithOrdersMapper.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="pers.oneToMany.dao.UserWithOrdersDao">

    <resultMap id="userAndOrder1" type="pers.oneToMany.entity.UserWithOrders">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <collection property="ordersList" ofType="pers.oneToMany.entity.Orders" column="id" select="pers.oneToMany.dao.OrdersDao.selectOrdersById"/>
    </resultMap>
    <select id="selectUserOrders1" parameterType="Integer" resultMap="userAndOrder1">
        select * from user where id=#{id}
    </select>

    <resultMap id="userAndOrder2" type="pers.oneToMany.entity.UserWithOrders">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="age" column="age" />
        <collection property="ordersList" ofType="pers.oneToMany.entity.Orders">
            <id property="id" column="id" />
            <result property="ordersn" column="ordersn"/>
        </collection>
    </resultMap>
    <select id="selectUserOrders2" parameterType="Integer" resultMap="userAndOrder2">
        select u.*,o.id,o.ordersn from user u,orders o where u.id = o.user_id and u.id = #{id}
    </select>

    <select id="selectUserOrders3" parameterType="Integer" resultType="pers.oneToMany.pojo.UserOrdersPOJO">
        select u.*,o.id,o.ordersn from user u,orders o where u.id = o.user_id and u.id = #{id}
    </select>
</mapper>

相比起一对一的级联,重点扭转的就是其中的<collection>,重要属性如下:

  • property:指定实体类的属性字段
  • ofType:指定汇合中的类型
  • column:将哪些值传递给 select 中的办法
  • select:嵌套查问的语句

第二个 <collection> 相似,将查问的后果间接映射到 Orders 的属性下面。最初一种是间接应用连贯查问。

6.2.4 Dao接口

public interface OrdersDao {List<Orders> selectOrdersById(Integer id);
}

public interface UserWithOrdersDao {UserWithOrders selectUserOrders1(Integer id);
    UserWithOrders selectUserOrders2(Integer id);
    List<UserOrdersPOJO> selectUserOrders3(Integer id);
}

6.2.5 测试

@Test
public void selectUserOrders()
{System.out.println(dao.selectUserOrders1(1));
    System.out.println(dao.selectUserOrders2(1));
    System.out.println(dao.selectUserOrders3(1));
}

6.3 多对多

MyBaits其实不反对多对多级联,然而能够通过多个一对多级联实现,比方一个订单对应多个商品,一个商品对应多个订单,这样两者就是多对多级联关系,这样应用一个两头表,就能够转换为两个一对多关系。

上面同样通过五个步骤实现多对多级联。

6.3.1 数据表

须要订单表、商品表以及一个两头表,因为订单表 Orders 之前已创立,这里只须要创立两个表:

use test;

create table product(id int(10) primary key auto_increment,
    name varchar(10) collate utf8mb4_unicode_ci default null,
    price double default null
);

create table orders_detail(id int(10) primary key auto_increment,
    orders_id int(10) default null,
    product_id int(10) default null,
    key orders_id(orders_id),
    key product_id(product_id),
    constraint orders_id foreign key (orders_id) references orders(id),
    constraint product_id foreign key (product_id) references product(id)
);

insert into product(`name`,`price`) values('product1',1.1),('product2',2.2),('product3',3.3);
insert into orders_detail(`orders_id`,`product_id`) values(1,1),(1,2),(1,3),(2,1),(2,3);

6.3.2 实体类

订单类能够沿用之前的,只须要两个实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    private Integer id;
    private String name;
    private Double price;
    private List<Orders> orders;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrdersWithProduct {
    private Integer id;
    private String ordersn;
    private List<Product> products;
}

6.3.3 映射文件

<?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="pers.manyToMany.dao.OrdersWithProductDao">
    <resultMap id="ordersAndProduct" type="pers.manyToMany.entity.OrdersWithProduct">
        <id property="id" column="id"/>
        <result property="ordersn" column="ordersn"/>
        <collection property="products" ofType="pers.manyToMany.entity.Product">
            <id property="id" column="pid"/>
            <result property="name" column="name"/>
            <result property="price" column="price"/>
        </collection>
    </resultMap>
    <select id="selectOrdersAndProduct" resultMap="ordersAndProduct">
        select o.*,p.id as pid ,p.name,p.price from orders o,orders_detail od, product p where o.id = od.orders_id and od.product_id = p.id
    </select>
</mapper>

这里的多对多级联本质上是通过每次指定不同的 OrdersId 去查问对应的 Product 实现的,也就是分成了屡次的一对多级联。

6.3.4 Dao接口

public interface OrdersWithProductDao {List<OrdersWithProduct> selectOrdersAndProduct();
}

6.3.5 测试

@Test
public void test()
{ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    OrdersWithProductDao dao = context.getBean(OrdersWithProductDao.class);
    System.out.println(dao.selectOrdersAndProduct());
}

7 动静SQL

最初来看一下动静 SQL,动静SQL 能够防止手动拼接 SQL 语句,比方在某些条件成立的状况下增加 and xxx=xxxx 之类的操作。先来看一下最罕用的<if>

7.1 <if>

<if>相似 Java 中的 if 语句,最简略的例子如下:

<select id="selectByIf" parameterType="Integer" resultType="pers.init.entity.User">
    select * from user where 1=1
    <if test="name != null and name !=''">
        and name like concat('%',#{name},'%')
    </if>
    <if test="age != null and age>0">
        and age = #{age}
    </if>
</select>

也就是说当 test 中的条件成立时,便增加 and xxx 语句。留神 test 这个属性是 <if> 必须的,不能省略。

(注这里用到了一个要害的 1=1,仅作阐明应用,理论开发请勿应用1=1 进行拼接)

Dao接口:

List<User> selectByIf(User user);

测试:

@Test
public void testIf()
{System.out.println(dao.selectByIf(User.builder().age((short) 33).name("111").build()));
}

7.2 <choose>+<when>+<otherwise>

相似 Java 中的 switch 语句:

  • <choose>相似switch
  • <when>相似case
  • <otherwise>相似default

当其中一个 <when> 成立时,语句便完结,相似于主动加上了"break"

示例:

<select id="selectByChoose" parameterType="Integer" resultType="pers.init.entity.User">
    select * from user where 1=1
    <choose>
        <when test="name != null and name !=''">
            and name like concat('%',#{name},'%')
        </when>
        <when test="age != null and age>0">
            and age=#{age}
        </when>
        <otherwise>
            and id != 3
        </otherwise>
    </choose>
</select>

Dao接口:

List<User> selectByChoose(User user);

测试:

@Test
public void testChoose()
{System.out.println(dao.selectByChoose(User.builder().age((short)33).build()));
}

7.3 <trim>

次要性能:

  • 加前缀
  • 加后缀
  • 替换某些首部 / 尾部内容

这里是一个应用 <trim> 来实现 <where> 的例子:

<select id="selectByTrim" parameterType="Integer" resultType="pers.init.entity.User">
    select * from user
    <trim prefix="where" prefixOverrides="and">
        <if test="name != null and name !=''">
            and name like concat('%',#{name},'%')
        </if>
    </trim>
</select>

Dao接口:

List<User> selectByTrim(User user);

测试:

@Test
public void testTrim()
{System.out.println(dao.selectByTrim(User.builder().build()));
    System.out.println(dao.selectByTrim(User.builder().name("test2").build()));
}

7.4 <where>

<where>最罕用的就是拼接查问条件,比方有多个查问条件,仅仅应用多个 <if> 的话会呈现首个 <if> 有一个多余的 and 的问题,而应用 <where> 会进行智能解决,当然也对 or 实用,例子如下:

<select id="selectByWhere" parameterType="Integer" resultType="pers.init.entity.User">
    select * from user
    <where>
        <if test="name != null and name !=''">
            and name like concat('%',#{name},'%')
        </if>
        <if test="age != null and age>0">
            and age=#{age}
        </if>
    </where>
</select>

Dao接口:

List<User> selectByWhere(User user);

测试:

@Test
public void testWhere()
{System.out.println(dao.selectByWhere(User.builder().build()));
    System.out.println(dao.selectByWhere(User.builder().name("111").build()));
    System.out.println(dao.selectByWhere(User.builder().age((short)-3).build()));
}

7.5 <set>

<set>个别配合 update 语句应用,比方:

<update id="updateBySet" parameterType="pers.init.entity.User">
    update user
    <set>
        <if test="name != null and name !=''">
            name = #{name},
        </if>
        <if test="age != null and age > 0">
            age = #{age}
        </if>
    </set>
    where id=#{id}
</update>

Dao接口:

int updateBySet(User user);

测试:

@Test
public void testSet()
{System.out.println(dao.updateBySet(User.builder().name("999999").age((short)39).id(1).build()));
    System.out.println(dao.selectByWhere(User.builder().build()));
}

7.6 <foreach>

<foreach>次要用于 in 中,能够认为是一个汇合,典型的应用场景是select xxx from xxx where xxx in <foreach>

<foreach>的次要属性有:

  • item:每个元素的别名
  • index:每个元素的下标
  • collection<foreach>的类型,有 listarraymap 三种,当传入单个参数且该参数类型为 List 时,则为list,传入单个参数且该参数类型为数组时,则为array,否则应将其封装成Map,并设置属性值为map
  • open:语句开始标记
  • close:语句完结标记

例子:

<select id="selectByForeach" parameterType="Integer" resultType="pers.init.entity.User">
  select * from user where id in
    <foreach collection="list" item="item" index="index" open="(" separator="," close=")">#{item}</foreach>
</select>

Dao接口:

List<User> selectByForeach(List<Integer> id);

测试:

@Test
public void testForeach()
{System.out.println(dao.selectByForeach(List.of(1,2,3)));
}

7.7 <bind>

<bind>可用于对字符串进行拼接,对于字符串拼接,MySQL应用的是 concat,而Oracle 应用的是 ||,而MyBatis 提供了 <bind> 能够屏蔽这种 DBMS 之间的差别,无需批改 xml 即可进行移植,例子如下:

<select id="selectByBind" parameterType="pers.init.entity.User" resultType="pers.init.entity.User">
    <bind name="new_name" value="'%'+name+'%'"/>
    select * from user where name like #{new_name}
</select>

Dao接口:

List<User> selectByBind(User user);

测试:

@Test
public void testBind()
{System.out.println(dao.selectByBind(User.builder().name("test1").build()));
}

8 源码

此处给出了实现所有例子后的代码,仅供参考,但不倡议间接clone,倡议从初始化工程开始逐渐实现。

Java版:

  • Github
  • 码云
  • CODE.CHINA

Kotlin版:

  • Github
  • 码云
  • CODE.CHINA
正文完
 0