SpringBoot学习笔记(五)——Spring Boot中使用MyBatis进阶

2022-05-09 19:55:06 浏览数 (1)

目录

  • 一、MyBatis配置文件详解
    • 1.1、settings全局参数配置
    • 1.2、typeAiases(别名)
    • 1.3、typeHandlers(类型处理器)
  • 二、MyBatis输入输出映射
    • 2.1、输入映射
      • 2.1.1、基本类型
      • 2.1.2、多个参数
      • 2.1.3、POJO对象
      • 2.1.4、Map
      • 2.1.5、集合类型
    • 2.2、输出映射
      • 2.2.1、ResultType
      • 2.2.2、ResultMap
      • 2.2.3、返回Map
  • 三、多表关联查询
    • 3.1、一对一关系
      • 3.1.1、执行环境
      • 3.1.2、关联查询(1次查询)
      • 3.1.3、嵌套查询(2次查询)
    • 3.2、一对多关系
      • 3.2.1、执行环境
      • 3.2.2、关联查询(1次查询)
      • 3.1.3、嵌套查询(多次查询)
  • 四、动态SQL
    • 4.0、MySQL环境与前置要求
    • 4.1、什么是动态SQL
    • 4.2、if条件判断
    • 4.3、where条件
    • 4.4、if set设置值
    • 4.5、choose(when,otherwise) 开关
    • 4.6、trim裁剪
    • 4.7、SQL 片段
    • 4.8、foreach循环
    • 4.9、bind 绑定变量
  • 五、视频
  • 六、示例
  • 七、作业

一、MyBatis配置文件详解

1.1、settings全局参数配置

mybatis框架运行时可以调整一些运行参数。比如,开启二级缓存,开启延迟加载等等。全局参数会影响mybatis的运行行为。

mybatis-settings的配置属性以及描述

setting(设置)

Description(描述)

valid Values(验证值组)

Default(默认值)

cacheEnabled

在全局范围内启用或禁用缓存配置 任何映射器在此配置下。

true | false

TRUE

lazyLoadingEnabled

在全局范围内启用或禁用延迟加载。禁用时,所有相关联的将热加载。

true | false

TRUE

aggressiveLazyLoading

启用时,有延迟加载属性的对象将被完全加载后调用懒惰的任何属性。否则,每一个属性是按需加载。

true | false

TRUE

multipleResultSetsEnabled

允许或不允许从一个单独的语句(需要兼容的驱动程序)要返回多个结果集。

true | false

TRUE

useColumnLabel

使用列标签,而不是列名。在这方面,不同的驱动有不同的行为。参考驱动文档或测试两种方法来决定你的驱动程序的行为如何。

true | false

TRUE

useGeneratedKeys

允许JDBC支持生成的密钥。兼容的驱动程序是必需的。此设置强制生成的键被使用,如果设置为true,一些驱动会不兼容性,但仍然可以工作。

true | false

FALSE

autoMappingBehavior

指定MyBatis的应如何自动映射列到字段/属性。NONE自动映射。 PARTIAL只会自动映射结果没有嵌套结果映射定义里面。 FULL会自动映射的结果映射任何复杂的(包含嵌套或其他)。

NONE,PARTIAL,FULL

PARTIAL

defaultExecutorType

配置默认执行人。SIMPLE执行人确实没有什么特别的。 REUSE执行器重用准备好的语句。 BATCH执行器重用语句和批处理更新。

SIMPLE,REUSE,BATCH

SIMPLE

safeRowBoundsEnabled

允许使用嵌套的语句RowBounds。

true | false

FALSE

mapUnderscoreToCamelCase

从经典的数据库列名A_COLUMN启用自动映射到骆驼标识的经典的Java属性名aColumn。

true | false

FALSE

localCacheScope

MyBatis的使用本地缓存,以防止循环引用,并加快反复嵌套查询。默认情况下(SESSION)会话期间执行的所有查询缓存。如果localCacheScope=STATMENT本地会话将被用于语句的执行,只是没有将数据共享之间的两个不同的调用相同的SqlSession。

SESSION STATEMENT

SESSION

dbcTypeForNull

指定为空值时,没有特定的JDBC类型的参数的JDBC类型。有些驱动需要指定列的JDBC类型,但其他像NULL,VARCHAR或OTHER的工作与通用值。

JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER

OTHER

lazyLoadTriggerMethods

指定触发延迟加载的对象的方法。

A method name list separated by commas

equals,clone,hashCode,toString

defaultScriptingLanguage

指定所使用的语言默认为动态SQL生成。

A type alias or fully qualified class name.

org.apache.ibatis.scripting.xmltags .XMLDynamicLanguageDriver

callSettersOnNulls

指定如果setter方法或map的put方法时,将调用检索到的值是null。它是有用的,当你依靠Map.keySet()或null初始化。注意(如整型,布尔等)不会被设置为null。

true | false

FALSE

logPrefix

指定的前缀字串,MyBatis将会增加记录器的名称。

Any String

Not set

logImpl

指定MyBatis的日志实现使用。如果此设置是不存在的记录的实施将自动查找。

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

Not set

proxyFactory

指定代理工具,MyBatis将会使用创建懒加载能力的对象。

CGLIB | JAVASSIST

CGLIB

官方文档settings的例子:

代码语言:javascript复制
<setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="multipleResultSetsEnabled" value="true"/>
    <setting name="useColumnLabel" value="true"/>
    <setting name="useGeneratedKeys" value="false"/>
    <setting name="autoMappingBehavior" value="PARTIAL"/>
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultStatementTimeout" value="25"/>
    <setting name="safeRowBoundsEnabled" value="false"/>
    <setting name="mapUnderscoreToCamelCase" value="false"/>
    <setting name="localCacheScope" value="SESSION"/>
    <setting name="jdbcTypeForNull" value="OTHER"/>
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

示例:

这里设置MyBatis的日志输出到控制台:

代码语言:javascript复制
mybatis:
  type-aliases-package: com.zhangguo.mybatisdemo.entity  #类型别名包的位置
  mapper-locations: classpath:/mapper/*.xml  #映射文件位置
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

结果:

1.2、typeAiases(别名)

在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。

如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。

如下所示类型com.zhangguo.mybatis02.entities.Student会反复出现,冗余:

代码语言:javascript复制
<?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.zhangguo.mybatis02.mapper.studentMapper">
    <select id="selectStudentById" resultType="com.zhangguo.mybatis02.entities.Student">
        SELECT id,name,sex from student where id=#{id}
    </select>

    <select id="selectStudentsByName" parameterType="String" resultType="com.zhangguo.mybatis02.entities.Student">
      SELECT id,name,sex from student where name like '%${value}%';
    </select>

    <insert id="insertStudent" parameterType="com.zhangguo.mybatis02.entities.Student">
        insert into student(name,sex) VALUES(#{name},'${sex}')
    </insert>

    <update id="updateStudent" parameterType="com.zhangguo.mybatis02.entities.Student">
        update student set name=#{name},sex=#{sex} where id=#{id}
    </update>

    <delete id="deleteStudent" parameterType="int">
        delete from student where id=#{id}
    </delete>

</mapper>

1.2.1.MyBatis默认支持的别名

别名

映射的类型

_byte

byte

_long

long

_short

short

_int

int

_integer

int

_double

double

_float

float

_boolean

boolean

string

String

byte

Byte

long

Long

short

Short

int

Integer

integer

Integer

double

Double

float

Float

boolean

Boolean

date

Date

decimal

BigDecimal

bigdecimal

BigDecimal

1.2.2.批量定义别名,扫描指定的包

定义单个别名的缺点很明显,如果项目中有很多别名则需要一个一个定义,且修改类型了还要修改配置文件非常麻烦,可以指定一个包,将下面所有的类都按照一定的规则定义成别名:

代码语言:javascript复制
mybatis:
  #指定包名下所有的类被自动扫描并定义默认别名,
  #mybatis会自动扫描包中的pojo类,自动定义别名,别名就是类名(首字母大写或小写都可以)
  type-aliases-package: com.zhangguo.mybatisdemo.entity  #类型别名包的位置
  mapper-locations: classpath:/mapper/*.xml  #映射文件位置
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

如果com.zhangguo.mybatis02.entities包下有一个名为Student的类,则使用别名时可以是:student,或Student。

你一定会想到当两个名称相同时的冲突问题,可以使用注解解决

解决方法:

1.3、typeHandlers(类型处理器)

mybatis中通过typeHandlers完成jdbc类型和java类型的转换。

通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义.

mybatis支持类型处理器:

类型处理器

Java类型

JDBC类型

BooleanTypeHandler

Boolean,boolean

任何兼容的布尔值

ByteTypeHandler

Byte,byte

任何兼容的数字或字节类型

ShortTypeHandler

Short,short

任何兼容的数字或短整型

IntegerTypeHandler

Integer,int

任何兼容的数字和整型

LongTypeHandler

Long,long

任何兼容的数字或长整型

FloatTypeHandler

Float,float

任何兼容的数字或单精度浮点型

DoubleTypeHandler

Double,double

任何兼容的数字或双精度浮点型

BigDecimalTypeHandler

BigDecimal

任何兼容的数字或十进制小数类型

StringTypeHandler

String

CHAR和VARCHAR类型

ClobTypeHandler

String

CLOB和LONGVARCHAR类型

NStringTypeHandler

String

NVARCHAR和NCHAR类型

NClobTypeHandler

String

NCLOB类型

ByteArrayTypeHandler

byte[]

任何兼容的字节流类型

BlobTypeHandler

byte[]

BLOB和LONGVARBINARY类型

DateTypeHandler

Date(java.util)

TIMESTAMP类型

DateOnlyTypeHandler

Date(java.util)

DATE类型

TimeOnlyTypeHandler

Date(java.util)

TIME类型

SqlTimestampTypeHandler

Timestamp(java.sql)

TIMESTAMP类型

SqlDateTypeHandler

Date(java.sql)

DATE类型

SqlTimeTypeHandler

Time(java.sql)

TIME类型

ObjectTypeHandler

任意

其他或未指定类型

EnumTypeHandler

Enumeration类型

VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。

二、MyBatis输入输出映射

2.1、输入映射

通过parameterType指定输入参数的类型,类型可以是简单类型、HashMap、POJO的包装类型。

Mybatis的配置文件中的select,insert,update,delete有一个属性parameter来接收mapper接口方法中的参数。可以接收的类型有简单类型和复杂类型,但是只能是一个参数。这个属性是可选的,因为Mybatis可以通过TypeHandler来判断传入的参数类型,默认值是unset。

2.1.1、基本类型

各种java的基本数据类型。常用的有int、String、Data等

接口:

代码语言:javascript复制
    /**
     * 根据学生编号获得学生对象
     */
    Student selectStudentById(int id);

映射:

代码语言:javascript复制
    <select id="selectStudentById" resultType="Student" parameterType="int">
        SELECT id,name,sex from student where id=#{id}
    </select>

测试:

代码语言:javascript复制
    /**
     * Method: selectStudentById(int id)
     */
    @Test
    public void testSelectStudentById() throws Exception {
        Student entity=dao.selectStudentById(1);
        System.out.println(entity);
        Assert.assertNotNull(entity);
    }

结果:

用#{变量名}来取值,这里的变量名是任意的,可以用value或者是其它的什么值,这里用id是为了便于理解,并不存在什么对应关系的。因为java反射主只能够得到方法参数的类型,而无从知道参数的名字的。当在动态sql中的if语句中的test传递参数时,就必须要用_parameter来传递参数了(OGNL表达式),如果你传入id就会报错。

2.1.2、多个参数

(一)、直接使用参数名称

该方法在低版本中可能出现错误

(二)、使用Map

接口:

代码语言:javascript复制
    /**
     * 根据学生姓名或性别获得学生集合
     */
    List<Student> selectStudentsByNameOrSex(Map<String,Object> params);

映射:

代码语言:javascript复制
    <select id="selectStudentsByNameOrSex" resultType="student">
        SELECT id,name,sex from student where name like '%${name}%' or sex=#{sex};
    </select>

测试:

代码语言:javascript复制
    /**
     * Method: List<Student> selectStudentsByNameOrSex(Map<String,Object> params);
     */
    @Test
    public void selectStudentsByNameOrSex() throws Exception {
        Map<String,Object> params=new HashMap<String,Object>();
        params.put("name","Candy");
        params.put("sex","girl");
        List<Student> students=dao.selectStudentsByNameOrSex(params);

        System.out.println(students);
        Assert.assertNotNull(students);
    }

结果:

(三)、注解参数名称:

接口:

代码语言:javascript复制
    /**
     * 根据学生姓名或性别获得学生集合
     */
    List<Student> selectStudentsByNameOrSex(@Param("realname") String name,@Param("sex") String sex);

映射:

代码语言:javascript复制
    <select id="selectStudentsByNameOrSex" resultType="student">
        SELECT id,name,sex from student where name like '%${realname}%' or sex=#{sex};
    </select>

测试:

代码语言:javascript复制
    /**
     * Method: selectStudentsByNameOrSex(String name,String sex)
     */
    @Test
    public void testSelectStudentsByNameOrSex() throws Exception {
        List<Student> students=dao.selectStudentsByNameOrSex("C","boy");
        System.out.println(students);
        Assert.assertNotNull(students);
    }

结果:

2.1.3、POJO对象

POJO是Plain OrdinaryJava Object的缩写,但是它通指没有使用Entity Beans的普通java对象,可以把POJO作为支持业务逻辑的协助类。

各种类型的POJO,取值用#{属性名}。这里的属性名是和传入的POJO中的属性名一一对应。

接口:

代码语言:javascript复制
    /**
     * 添加学生
     */
    int insertStudent(Student entity);

映射:

代码语言:javascript复制
    <insert id="insertStudent" parameterType="student">
        insert into student(name,sex) VALUES(#{name},'${sex}')
    </insert>

测试:

代码语言:javascript复制
    /**
     * Method: insertStudent
     */
    @Test
    public void testInsertStudent() throws Exception {
        Student entity=new Student();
        entity.setName("张明");
        entity.setSex("boy");

        Assert.assertEquals(1,dao.insertStudent(entity));
    }

结果:

如果要在if元素中测试传入的user参数,仍然要使用_parameter来引用传递进来的实际参数,因为传递进来的User对象的名字是不可考的。如果测试对象的属性,则直接引用属性名字就可以了。 测试user对象:

代码语言:javascript复制
<if test="_parameter!= null">

测试user对象的属性:

代码语言:javascript复制
<if test="name!= null">

如果对象中还存在对象则需要使用${属性名.属性.x}方式访问

2.1.4、Map

具体请查看2.1.2节。

传入map类型,直接通过#{keyname}就可以引用到键对应的值。使用@param注释的多个参数值也会组装成一个map数据结构,和直接传递map进来没有区别。

mapper接口:

代码语言:javascript复制
int updateByExample(@Param("user") User user, @Param("example") UserExample example);

sql映射:

代码语言:javascript复制
<update id="updateByExample" parameterType="map" > 

update tb_user set id = #{user.id}, ... 

<if test="_parameter != null" > 

<include refid="Update_By_Example_Where_Clause" />

</if>

</update>

注意这里测试传递进来的map是否为空,仍然使用_parameter

2.1.5、集合类型

可以传递一个List或Array类型的对象作为参数,MyBatis会自动的将List或Array对象包装到一个Map对象中,List类型对象会使用list作为键名,而Array对象会用array作为键名。集合类型通常用于构造IN条件,sql映射文件中使用foreach元素来遍历List或Array元素。

假定这里需要实现多删除功能,示例如下:

接口:

代码语言:javascript复制
    /**
     * 删除多个学生通过编号
     */
    int deleteStudents(List<Integer> ids);

映射:

代码语言:javascript复制
    <delete id="deleteStudents">
        delete from student where id in
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

collection这里只能是list

测试:

代码语言:javascript复制
    /**
     * Method: deleteStudents
     */
    @Test
    public void testDeleteStudents() throws Exception {
        List<Integer> ids=new ArrayList<Integer>();
        ids.add(10);
        ids.add(11);
        Assert.assertEquals(2,dao.deleteStudents(ids));
    }

结果:

当然查询中也可以这样使用

代码语言:javascript复制
public List<XXXBean> getXXXBeanList(List<String> list);  

<select id="getXXXBeanList" resultType="XXBean">
  select 字段... from XXX where id in
  <foreach item="item" index="index" collection="list" open="(" separator="," close=")">  
    #{item}  
  </foreach>  
</select>  

foreach 最后的效果是select 字段... from XXX where id in ('1','2','3','4') 

对于单独传递的List或Array,在SQL映射文件中映射时,只能通过list或array来引用。但是如果对象类型有属性的类型为List或Array,则在sql映射文件的foreach元素中,可以直接使用属性名字来引用。 mapper接口: 

代码语言:javascript复制
List<User> selectByExample(UserExample example);

sql映射文件: 

代码语言:javascript复制
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
</where>

在这里,UserExample有一个属性叫oredCriteria,其类型为List,所以在foreach元素里直接用属性名oredCriteria引用这个List即可。

item="criteria"表示使用criteria这个名字引用每一个集合中的每一个List或Array元素。

使用数组与可变参数

代码语言:javascript复制
List<Category> findByIds(int... ids);

sql映射文件: 

代码语言:javascript复制
    <select id="findByIds" resultType="Category">
        SELECT id,name from category where id in
        <foreach collection="array" open="(" close=")" separator=",">
            #{ids}
        </foreach>
    </select>

2.2、输出映射

输出映射主要有两种方式指定ResultType或ResultMap,现在分别介绍一下:

2.2.1、ResultType

使用ResultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。

如果查询出来的列名和POJO中的属性名全部不一致,没有创建POJO对象。

只要查询出来的列名和POJO中的属性有一个一致,就会创建POJO对象。

(一)、输出简单类型

接口:

代码语言:javascript复制
    /**
     * 获得学生总数
     * */
    long selectStudentsCount();

映射:

代码语言:javascript复制
    <select id="selectStudentsCount" resultType="long">
        SELECT count(*) from student
    </select>

测试:

代码语言:javascript复制
    /**
     * Method: selectStudentsCount()
     */
    @Test
    public void testSelectStudentsCount() throws Exception {
        Assert.assertNotEquals(0,dao.selectStudentsCount());
    }

结果:

查询出来的结果集只有一行一列,可以使用简单类型进行输出映射。

(二)、输出POJO对象和POJO列表

不管是输出的POJO单个对象还是一个列表(List中存放POJO),在mapper.xml中ResultType指定的类型是一样的,但方法返回值类型不一样。

输出单个POJO对象,方法返回值是单个对象类型

接口:

代码语言:javascript复制
    /**
     * 根据学生编号获得学生对象
     */
    Student selectStudentById(int id);

映射:

代码语言:javascript复制
    <select id="selectStudentById" resultType="Student">
        SELECT id,name,sex from student where id=#{id}
    </select>

输出pojo对象list,方法返回值是List<POJO>

接口:

代码语言:javascript复制
    /**
     * 根据学生姓名获得学生集合
     */
    List<Student> selectStudentsByName(String name);

映射:

代码语言:javascript复制
    <select id="selectStudentsByName" parameterType="String" resultType="student">
        SELECT id,name,sex from student where name like '%${value}%';
    </select>

生成的动态代理对象中是根据mapper.java方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList(返回集合对象调用)

2.2.2、ResultMap

MyBatis中使用ResultMap完成自定义输出结果映射,如一对多,多对多关联关系。

问题:

假定POJO对象与表中的字段不一致,如下所示:

接口:

代码语言:javascript复制
    /**
     * 根据性别获得学生集合
     */
    List<Stu> selectStudentsBySex(String sex);

映射:

代码语言:javascript复制
    <select id="selectStudentsBySex" parameterType="String" resultType="stu">
        SELECT id,name,sex from student where sex=#{sex};
    </select>

测试:

代码语言:javascript复制
    /**
     * Method: selectStudentsBySex(String sex)
     */
    @Test
    public void testSelectStudentsBySex() throws Exception {
        List<Stu> students=dao.selectStudentsBySex("boy");
        System.out.println(students);
        Assert.assertNotNull(students.get(0));
    }

结果:

(一)、定义并引用ResultMap

修改映射文件:

代码语言:javascript复制
    <!--定义结果映射,id是引用时的编号需唯一,stu是最终被映射的类型-->
    <resultMap id="stuMap" type="stu">
        <!--映射结果,collumn表示列名,property表示属性名-->
        <result column="id" property="stu_id"></result>
        <result column="name" property="stu_name"></result>
        <result column="sex" property="stu_sex"></result>
    </resultMap>
    
    <!--resultMap指定引用的映射-->
    <select id="selectStudentsBySex" parameterType="String" resultMap="stuMap">
        SELECT id,name,sex from student where sex=#{sex};
    </select>

测试结果:

(二)、使用别名

 修改映射文件:

代码语言:javascript复制
    <select id="selectStudentsBySex" parameterType="String" resultType="stu">
      SELECT id stu_id,name stu_name,sex as stu_sex from student where sex=#{sex};
    </select>

测试结果:

(三)、注解上使用Result

代码语言:javascript复制
    @Results({
            @Result(property ="id",column = "id"),
            @Result(property = "name",column = "name")
    })
    @Select("select * from Category where id=#{id}")
    Category findById(int id);

2.2.3、返回Map

假定要返回id作为key,name作为value的Map。

接口:

代码语言:javascript复制
    /**
     * 获得所有学生Map集合
     */
    List<Map<String,Object>> selectAllStudents();

映射:

代码语言:javascript复制
    <resultMap id="stuKeyValueMap" type="HashMap">
        <result property="name" column="NAME"></result>
        <result property="value" column="VALUE"></result>
    </resultMap>

    <select id="selectAllStudents" resultMap="stuKeyValueMap">
        SELECT id NAME,name VALUE from student;
    </select>

测试:

代码语言:javascript复制
   /**
     * Method: selectAllStudents()
     */
    @Test
    public void testSelectAllStudents() throws Exception {
        List<Map<String,Object>>  students=dao.selectAllStudents();
        System.out.println(students);
        Assert.assertNotNull(students);
    }

结果:

代码语言:javascript复制
<resultMap id="pieMap"   type="HashMap">  
    <result property="value" column="VALUE" />  
    <result property="name" column="NAME" />  
</resultMap>

<select id="queryPieParam" parameterType="String" resultMap="pieMap">
    SELECT
      PLAT_NAME NAME,
        <if test='_parameter == "总量"'>
            AMOUNT VALUE
        </if>
        <if test='_parameter == "总额"'>
            TOTALS VALUE
        </if>
    FROM
        DOMAIN_PLAT_DEAL_PIE
    ORDER BY
        <if test='_parameter  == "总量"'>
            AMOUNT
        </if>
        <if test='_parameter  == "总额"'>
            TOTALS
        </if>
    ASC
</select>

用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。

如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。

 最终完成的映射器:

代码语言:javascript复制
<?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.zhangguo.mybatis03.dao.StudentMapper">

    <select id="selectStudentById" resultType="Student">
        SELECT id,name,sex from student where id=#{id}
    </select>

    <select id="selectStudentsCount" resultType="long">
        SELECT count(*) from student
    </select>

    <select id="selectStudentsByName" parameterType="String" resultType="student">
        SELECT id,name,sex from student where name like '%${value}%';
    </select>

    <resultMap id="stuKeyValueMap" type="HashMap">
        <result property="name" column="NAME"></result>
        <result property="value" column="VALUE"></result>
    </resultMap>

    <select id="selectAllStudents" resultMap="stuKeyValueMap">
        SELECT id NAME,name VALUE from student;
    </select>


    <!--定义结果映射,id是引用时的编号需唯一,stu是最终被映射的类型-->
    <resultMap id="stuMap" type="stu">
        <!--映射结果,collumn表示列名,property表示属性名-->
        <result column="id" property="stu_id"></result>
        <result column="name" property="stu_name"></result>
        <result column="sex" property="stu_sex"></result>
    </resultMap>

    <!--resultMap指定引用的映射-->
    <!--<select id="selectStudentsBySex" parameterType="String" resultMap="stuMap">-->
        <!--SELECT id,name,sex from student where sex=#{sex};-->
    <!--</select>-->

    <select id="selectStudentsBySex" parameterType="String" resultType="stu">
      SELECT id stu_id,name stu_name,sex as stu_sex from student where sex=#{sex};
    </select>


    <select id="selectStudentsByNameOrSex" resultType="student">
      SELECT id,name,sex from student where name like '%${realname}%' or sex=#{sex};
    </select>

    <select id="selectStudentsByIdOrSex" resultType="student">
        SELECT id,name,sex from student where id=#{no} or sex=#{sex};
    </select>


    <insert id="insertStudent" parameterType="student">
        insert into student(name,sex) VALUES(#{name},'${sex}')
    </insert>

    <update id="updateStudent" parameterType="student">
        update student set name=#{name},sex=#{sex} where id=#{id}
    </update>

    <delete id="deleteStudent" parameterType="int">
        delete from student where id=#{id}
    </delete>

    <delete id="deleteStudents">
        delete from student where id in
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

</mapper>

 最终完成的数据访问类似:

代码语言:javascript复制
package com.zhangguo.mybatis03.dao;

import com.zhangguo.mybatis03.entities.Stu;
import com.zhangguo.mybatis03.entities.Student;
import com.zhangguo.mybatis03.utils.SqlSessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.List;
import java.util.Map;

public class StudentDao implements StudentMapper {

    /**
     * 根据学生编号获得学生对象
     */
    public Student selectStudentById(int id) {
        Student entity = null;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //查询单个对象,指定参数为3
        entity = mapper.selectStudentById(id);

        //关闭
        SqlSessionFactoryUtil.closeSession(session);

        return entity;
    }

    /**
     * 获得学生总数
     */
    public long selectStudentsCount() {
        long count = 0;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //查询单行单列,简单值
        count = mapper.selectStudentsCount();

        //关闭
        SqlSessionFactoryUtil.closeSession(session);

        return count;
    }


    /**
     * 根据学生姓名获得学生集合
     */
    public List<Student> selectStudentsByName(String name) {
        List<Student> entities = null;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //查询多个对象,指定参数
        entities = mapper.selectStudentsByName(name);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return entities;
    }

    /**
     * 获得所有学生Map集合
     *
     */
    public List<Map<String, Object>> selectAllStudents() {
        List<Map<String, Object>> entities = null;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //查询多个对象,指定参数
        entities = mapper.selectAllStudents();
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return entities;
    }

    /**
     * 根据性别获得学生集合
     *
     * @param sex
     */
    public List<Stu> selectStudentsBySex(String sex) {
        List<Stu> entities = null;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //查询多个对象,指定参数
        entities = mapper.selectStudentsBySex(sex);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return entities;
    }

    /**
     * 根据学生姓名或性别获得学生集合
     *
     * @param name
     * @param sex
     */
    public List<Student> selectStudentsByNameOrSex(String name, String sex) {
        List<Student> entities = null;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //查询多个对象,指定参数
        entities = mapper.selectStudentsByNameOrSex(name, sex);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return entities;
    }

    /**
     * 根据学生Id或性别获得学生集合
     *
     * @param param
     */
    public List<Student> selectStudentsByIdOrSex(Map<String, Object> param) {
        List<Student> entities = null;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //查询多个对象,指定参数
        entities = mapper.selectStudentsByIdOrSex(param);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return entities;
    }


    /**
     * 添加学生
     */
    public int insertStudent(Student entity) {
        //影响行数
        int rows = 0;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //执行添加
        rows = mapper.insertStudent(entity);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return rows;
    }

    /**
     * 更新学生
     */
    public int updateStudent(Student entity) {
        //影响行数
        int rows = 0;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //执行更新
        rows = mapper.updateStudent(entity);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return rows;
    }

    /**
     * 删除学生
     */
    public int deleteStudent(int id) {
        //影响行数
        int rows = 0;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //执行删除
        rows = mapper.deleteStudent(id);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return rows;
    }

    /**
     * 删除多个学生通过编号
     *
     * @param ids
     */
    public int deleteStudents(List<Integer> ids) {
        //影响行数
        int rows = 0;
        //打开一个会话
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //获得一个映射器
        StudentMapper mapper = session.getMapper(StudentMapper.class);

        //执行删除
        rows = mapper.deleteStudents(ids);
        //关闭
        SqlSessionFactoryUtil.closeSession(session);
        return rows;
    }

}

 最终完成的接口:

代码语言:javascript复制
package com.zhangguo.mybatis03.dao;

import com.zhangguo.mybatis03.entities.Stu;
import com.zhangguo.mybatis03.entities.Student;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface StudentMapper {
    /**
     * 根据学生编号获得学生对象
     */
    Student selectStudentById(int id);

    /**
     * 获得学生总数
     * */
    long selectStudentsCount();

    /**
     * 根据学生姓名获得学生集合
     */
    List<Student> selectStudentsByName(String name);


    /**
     * 获得所有学生Map集合
     */
    List<Map<String,Object>> selectAllStudents();

    /**
     * 根据性别获得学生集合
     */
    List<Stu> selectStudentsBySex(String sex);

    /**
     * 根据学生姓名或性别获得学生集合
     */
    List<Student> selectStudentsByNameOrSex(@Param("realname") String name,@Param("sex") String sex);

    /**
     * 根据学生Id或性别获得学生集合
     */
    List<Student> selectStudentsByIdOrSex(Map<String,Object> param);


    /**
     * 添加学生
     */
    int insertStudent(Student entity);

    /**
     * 更新学生
     */
    int updateStudent(Student entity);

    /**
     * 删除学生
     */
    int deleteStudent(int id);

    /**
     * 删除多个学生通过编号
     */
    int deleteStudents(List<Integer> ids);
}

 最终完成的测试:

代码语言:javascript复制
package com.zhangguo.mybatis03.dao;

import com.zhangguo.mybatis03.entities.Stu;
import com.zhangguo.mybatis03.entities.Student;
import org.junit.*;
import org.junit.runners.MethodSorters;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * StudentDao Tester.
 *
 * @author <Authors name>
 * @version 1.0
 * @since <pre>09/26/2018</pre>
 */
@FixMethodOrder(MethodSorters.JVM)//指定测试方法按定义的顺序执行
public class StudentDaoTest {
    StudentMapper dao;
    @Before
    public void before() throws Exception {
        dao=new StudentDao();
    }

    @After
    public void after() throws Exception {
    }

    /**
     * Method: selectStudentById(int id)
     */
    @Test
    public void testSelectStudentById() throws Exception {
        Student entity=dao.selectStudentById(1);
        System.out.println(entity);
        Assert.assertNotNull(entity);
    }

    //
    /**
     * Method: selectStudentsCount()
     */
    @Test
    public void testSelectStudentsCount() throws Exception {
        Assert.assertNotEquals(0,dao.selectStudentsCount());
    }
    /**
     * Method: selectStudentsByName(String name)
     */
    @Test
    public void testSelectStudentsByName() throws Exception {
        List<Student> students=dao.selectStudentsByName("C");
        System.out.println(students);
        Assert.assertNotNull(students);
    }

    /**
     * Method: selectAllStudents()
     */
    @Test
    public void testSelectAllStudents() throws Exception {
        List<Map<String,Object>>  students=dao.selectAllStudents();
        System.out.println(students);
        Assert.assertNotNull(students);
    }

    /**
     * Method: selectStudentsBySex(String sex)
     */
    @Test
    public void testSelectStudentsBySex() throws Exception {
        List<Stu> students=dao.selectStudentsBySex("boy");
        System.out.println(students);
        Assert.assertNotNull(students.get(0));
    }


    /**
     * Method: selectStudentsByIdOrSex
     */
    @Test
    public void testSelectStudentsByNameOrSex() throws Exception {
        Map<String ,Object> param=new HashMap<String,Object>();
        param.put("no",1);
        param.put("sex","girl");
        List<Student> students=dao.selectStudentsByIdOrSex(param);
        System.out.println(students);
        Assert.assertNotNull(students);
    }

    /**
     * Method: insertStudent
     */
    @Test
    public void testInsertStudent() throws Exception {
        Student entity=new Student();
        //entity.setName("张明");
        entity.setSex("boy");

        Assert.assertEquals(1,dao.insertStudent(entity));
    }

    /**
     * Method: updateStudent
     */
    @Test
    public void testUpdateStudent() throws Exception {
        Student entity=dao.selectStudentById(11);
        //entity.setName("张丽美");
        entity.setSex("girl");

        Assert.assertEquals(1,dao.updateStudent(entity));
    }

    /**
     * Method: deleteStudent
     */
    @Test
    public void testDeleteStudent() throws Exception {
        Assert.assertEquals(1,dao.deleteStudent(12));
    }



    /**
     * Method: deleteStudents
     */
    @Test
    public void testDeleteStudents() throws Exception {
        List<Integer> ids=new ArrayList<Integer>();
        ids.add(10);
        ids.add(11);
        Assert.assertEquals(2,dao.deleteStudents(ids));
    }
} 

一、多表关联查询

表与表之间有三种常见的关联关系,分别是一对一,一对多与多对多关系,MyBatis直接提供一对一与一对多的关联关系,可能通过间接的方式实现一对多关联。

1.1、一对一关系

1.1.1、执行环境

假定一个员工(emp)拥有一个登录用户(user),员工与用户表之间是一对一关系:

 用户表:

员工表:

SQL:

代码语言:javascript复制
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
 `username` varchar(64) NOT NULL COMMENT '用户名',
 `password` varchar(64) NOT NULL COMMENT '密码',
 PRIMARY KEY (`id`),
 UNIQUE KEY `users_username_uindex` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='用户表';

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'tom', '123456');
INSERT INTO `user` VALUES ('2', 'rose', '888888');
INSERT INTO `user` VALUES ('3', 'mark', 'qwerty');
INSERT INTO `user` VALUES ('4', 'jack', 'qaz123');
INSERT INTO `user` VALUES ('5', 'mali', 'uio890');

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `emp`
-- ----------------------------
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
 `user_id` int(11) DEFAULT NULL COMMENT '用户编号',
 `realname` varchar(32) NOT NULL COMMENT '姓名',
 `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
 PRIMARY KEY (`id`),
 KEY `emp_user_id` (`user_id`),
 CONSTRAINT `emp_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='员工表';

-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES ('1', '1', '汤姆', 'tom@gmail.com');
INSERT INTO `emp` VALUES ('2', '2', '梅贵', 'rose@163.com');
INSERT INTO `emp` VALUES ('3', '3', '马克', 'mark@sina.com');
INSERT INTO `emp` VALUES ('4', '4', '岳翰', 'jack@gmail.com');
INSERT INTO `emp` VALUES ('5', '5', '马丽', 'mali@sina.com');

关系:

1.1.2、关联查询(1次查询)

实体:

  用户:

代码语言:javascript复制
package com.zhangguo.mybatis03.entities;

/**用户POJO*/
public class User {
 private int id;
 private String username;
 private String password;

 public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public String getUsername() {
 return username;
 }

 public void setUsername(String username) {
 this.username = username;
 }

 public String getPassword() {
 return password;
 }

 public void setPassword(String password) {
 this.password = password;
 }
}

  员工:

代码语言:javascript复制
package com.zhangguo.mybatis03.entities;

/**员工POJO*/
public class Emp {
 private int id;
 /**用户编号*/
 private int user_id;
 private String realname;
 private String email;

 /**用户对象*/
 private User user;

 public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public int getUser_id() {
 return user_id;
 }

 public void setUser_id(int user_id) {
 this.user_id = user_id;
 }

 public String getRealname() {
 return realname;
 }

 public void setRealname(String realname) {
 this.realname = realname;
 }

 public String getEmail() {
 return email;
 }

 public void setEmail(String email) {
 this.email = email;
 }

 public User getUser() {
 return user;
 }

 public Emp setUser(User user) {
 this.user = user;
 return this;
 }
}

接口:

代码语言:javascript复制
package com.zhangguo.mybatis03.dao;

import com.zhangguo.mybatis03.entities.Emp;

/**员工数据访口*/
public interface EmpMapper {

 /**获得员工通过员工编号*/
 Emp getEmpById_1(int id);

}

映射:

代码语言:javascript复制
<?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.zhangguo.mybatis03.dao.EmpMapper">

 <!--一对一查询,方法1,通过内联接-->
 <select id="getEmpById_1" resultMap="empMap_1" parameterType="int">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email,
 `user`.username,
 `user`.`password`
 FROM
 emp
 INNER JOIN `user` ON emp.user_id = `user`.id where emp.id=#{id}
 </select>

 <!--员工关联查询结果映射-->
 <resultMap id="empMap_1" type="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 <!--映射关系,指定属性与属性的类型-->
 <association property="user" javaType="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 </association>
 </resultMap>

</mapper>

测试:

代码语言:javascript复制
package com.zhangguo.mybatis03.dao;

import com.zhangguo.mybatis03.entities.Emp;
import org.junit.Assert;
import org.junit.Test;
import org.junit.Before;
import org.junit.After;

/**
 * EmpDao Tester.
 *
 * @author <Authors name>
 * @version 1.0
 * @since <pre>09/30/2018</pre>
 */
public class EmpDaoTest {
 EmpMapper empDao;
 @Before
 public void before() throws Exception {
 empDao=new EmpDao();
 }

 @After
 public void after() throws Exception {
 }

 /**
 * Method: getEmpById_1(int id)
 * 获得员工通过员工编号
 */
 @Test
 public void testGetEmpById_1() throws Exception {
 Emp entity=empDao.getEmpById_1(1);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }


} 

结果:

1.1.3、嵌套查询(2次查询)

实体:同上

接口:

代码语言:javascript复制
 /**获得员工通过员工编号,多次查询*/
 Emp getEmpById_2(int id);

映射:

代码语言:javascript复制
 <!--一对一查询,方法2,通过多次查询(嵌套查询)-->
 <select id="getEmpById_2" resultMap="empMap_2">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email
 FROM
 emp where id=#{id}
 </select>

 <!--员工多次查询结果映射-->
 <resultMap id="empMap_2" type="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 <!--通过外键user_id再次发起查询,调用selectUserById获得User对象-->
 <association property="user" column="user_id" select="selectUserById"></association>
 </resultMap>

 <!--根据用户编号获得用户对象-->
 <select id="selectUserById" resultType="User">
 SELECT
 `user`.id,
 `user`.username,
 `user`.`password`
 FROM
 `user` where id=#{id}
 </select>

测试:

代码语言:javascript复制
 /**
 * Method: getEmpById_2(int id)
 * 获得员工通过员工编号,一对一方法二
 */
 @Test
 public void testGetEmpById_2() throws Exception {
 Emp entity=empDao.getEmpById_2(2);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }

结果:

MyBatis中使用association标签来解决一对一的关联查询,association标签可用的属性如下:

  • property:对象属性的名称
  • javaType:对象属性的类型
  • column:所对应的外键字段名称
  • select:使用另一个查询封装的结果

1.2、一对多关系

1.2.1、执行环境

一个用户帐号可以被多个员工使用,形成一个一对多的关系,表中的数据如下:

员工表emp:

用户表user:

1.2.2、关联查询(1次查询)

实体:

  员工:

代码语言:javascript复制
package com.zhangguo.mybatis03.entities;

/**员工POJO*/
public class Emp {
 private int id;
 /**用户编号*/
 private int user_id;
 private String realname;
 private String email;

 /**用户对象*/
 private User user;

 public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public int getUser_id() {
 return user_id;
 }

 public void setUser_id(int user_id) {
 this.user_id = user_id;
 }

 public String getRealname() {
 return realname;
 }

 public void setRealname(String realname) {
 this.realname = realname;
 }

 public String getEmail() {
 return email;
 }

 public void setEmail(String email) {
 this.email = email;
 }

 public User getUser() {
 return user;
 }

 public Emp setUser(User user) {
 this.user = user;
 return this;
 }

 @Override
 public String toString() {
 return "Emp{"  
 "id="   id  
 ", user_id="   user_id  
 ", realname='"   realname   '''  
 ", email='"   email   '''  
 ", user="   user  
 '}';
 }
}

  用户:

代码语言:javascript复制
package com.zhangguo.mybatis03.entities;

import java.util.List;

/**用户POJO*/
public class User {
 private int id;
 private String username;
 private String password;

 /**员工集合,一个用户对象对应多个员工对象*/
 private List<Emp> emps;

 public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public String getUsername() {
 return username;
 }

 public void setUsername(String username) {
 this.username = username;
 }

 public String getPassword() {
 return password;
 }

 public void setPassword(String password) {
 this.password = password;
 }

 public List<Emp> getEmps() {
 return emps;
 }

 public User setEmps(List<Emp> emps) {
 this.emps = emps;
 return this;
 }

 @Override
 public String toString() {
 return "User{"  
 "id="   id  
 ", username='"   username   '''  
 ", password='"   password   '''  
 ", emps="   emps  
 '}';
 }
}

接口:

代码语言:javascript复制
 /**获得用户通过用户编号,1对多级联查询*/
 User getUserById_1(int id);

映射:

代码语言:javascript复制
 <!--一对多查询,方法1,通过内联接-->
 <select id="getUserById_1" resultMap="userMap_1" parameterType="int">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email,
 `user`.username,
 `user`.`password`
 FROM
 emp
 INNER JOIN `user` ON emp.user_id = `user`.id
 where `user`.id=#{id}
 </select>

 <resultMap id="userMap_1" type="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
<!--将emps对象映射成一个集合,emps是user类型中的属性,ofType用于指定集合中存放的对象类型-->
 <collection property="emps" ofType="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 </collection>
 </resultMap>

测试:

代码语言:javascript复制
 /**
 * Method: getUserById_1(int id)
 * 获得用户过用户编号,级联查询
 */
 @Test
 public void testGetUserById_1() throws Exception {
 User entity=empDao.getUserById_1(2);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }

结果:

上面的示例中会发现User对象中包含多个Emp对象,此时的Emp对象中又引用了User对象,但值是空的,如果想设置值可以继续用1对1的办法赋值:

映射:

代码语言:javascript复制
 <resultMap id="userMap_1" type="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 <!--将emps对象映射成一个集合,emps是user类型中的属性,ofType用于指定集合中存放的对象类型-->
 <collection property="emps" ofType="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 <!--映射关系,指定属性与属性的类型-->
 <association property="user" javaType="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 </association>
 </collection>
 </resultMap>

结果:

1.1.3、嵌套查询(多次查询)

实体:同上

接口:

代码语言:javascript复制
 /**获得用户通过用户编号,1对多嵌套查询*/
 User getUserById_2(int id);

映射:

代码语言:javascript复制
 <!--一对多查询,方法2,通过嵌套查询多次-->
 <select id="getUserById_2" resultMap="userMap_2" parameterType="int">
 SELECT
 `user`.id,
 `user`.username,
 `user`.`password`
 FROM
 `user` where id=#{id}
 </select>

 <resultMap id="userMap_2" type="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 <!--将emps对象映射成一个集合,emps是user类型中的属性,ofType用于指定集合中存放的对象类型-->
 <!--select用于指定再次查询的SQL编号,column用于指定参数列-->
 <collection property="emps" ofType="Emp" column="id" select="selectEmpById"></collection>
 </resultMap>

 <!--根据员工编号获得员工对象-->
 <select id="selectEmpById" resultType="Emp">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email
 FROM
 emp where user_id=#{id}
 </select>

测试:

代码语言:javascript复制
 /**
 * Method: getUserById_2(int id)
 * 获得用户过用户编号,嵌套查询
 */
 @Test
 public void testGetUserById_2() throws Exception {
 User entity=empDao.getUserById_2(5);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }

结果:

MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。

二、动态SQL

2.0、MySQL环境与前置要求

数据与SQL环境如下:

前置要求:

2.1、什么是动态SQL

MyBatis的动态SQL是基于OGNL的表达式的。它对SQL语句进行灵活的操作,通过表达式判断来实现对SQL的灵活拼接、组装。

 mybatis核心对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。

 主要通过以下标签:if,where,choose(when,otherwise),trim,set,foreach。

2.2、if条件判断

根据 name和 sex 来查询数据。如果name为空,那么将只根据sex来查询;反之只根据name来查询

首先不使用 动态SQL 来书写

接口:

代码语言:javascript复制
    /**
     * 根据学生姓名和性别获得学生集合
     */
    List<Student> selectStudentsByNameAndSex(@Param("name") String name,@Param("sex") String sex);

映射:

代码语言:javascript复制
    <select id="selectStudentsByNameAndSex" resultType="student">
        SELECT id,name,sex from student where name=#{name} and sex=#{sex};
    </select>

测试:

代码语言:javascript复制
    /**
     * Method: selectStudentsByNameAndSex
     */
    @Test
    public void testSelectStudentsByNameAndSex() throws Exception {
        List<Student> students=dao.selectStudentsByNameAndSex("rose",null);
        System.out.println(students);
        Assert.assertNotNull(students);
    }

结果:

上面的查询语句,我们发现如果 #{sex} 为空,那么查询结果也是空,如何解决这个问题呢?使用 if 来判断

代码语言:javascript复制
    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student where 1=1
        <!--如果test为真会输出中间的内容-->
        <if test="name!=null and name!=''">
            and name=#{name}
        </if>

        <if test="sex!=null and sex!=''">
            and sex=#{sex}
        </if>

    </select>

结果:

参考:

代码语言:javascript复制
<!-- 2 if(判断参数) - 将实体类不为空的属性作为where条件 --> 
<select id="getStudentList_if" resultMap="resultMap_studentEntity" parameterType="liming.student.manager.data.model.StudentEntity"> 
 SELECT ST.STUDENT_ID, 
 ST.STUDENT_NAME, 
 ST.STUDENT_SEX, 
 ST.STUDENT_BIRTHDAY, 
 ST.STUDENT_PHOTO, 
 ST.CLASS_ID, 
 ST.PLACE_ID 
 FROM STUDENT_TBL ST 
 WHERE 
 <if test="studentName !=null "> 
 ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName, jdbcType=VARCHAR}),'%') 
 </if> 
 <if test="studentSex != null and studentSex != '' "> 
 AND ST.STUDENT_SEX = #{studentSex, jdbcType=INTEGER} 
 </if> 
 <if test="studentBirthday != null "> 
 AND ST.STUDENT_BIRTHDAY = #{studentBirthday, jdbcType=DATE} 
 </if> 
 <if test="classId != null and classId!= '' "> 
 AND ST.CLASS_ID = #{classId, jdbcType=VARCHAR} 
 </if> 
 <if test="classEntity != null and classEntity.classId !=null and classEntity.classId !=' ' "> 
 AND ST.CLASS_ID = #{classEntity.classId, jdbcType=VARCHAR} 
 </if> 
 <if test="placeId != null and placeId != '' "> 
 AND ST.PLACE_ID = #{placeId, jdbcType=VARCHAR} 
 </if> 
 <if test="placeEntity != null and placeEntity.placeId != null and placeEntity.placeId != '' "> 
 AND ST.PLACE_ID = #{placeEntity.placeId, jdbcType=VARCHAR} 
 </if> 
 <if test="studentId != null and studentId != '' "> 
 AND ST.STUDENT_ID = #{studentId, jdbcType=VARCHAR} 
 </if> 
</select> 

 虽然1=1这种方法结合if可以解决我们的需求,但是1=1明显是冗余的,通过where可以解决。

2.3、where条件

where 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。

修改后的映射:

代码语言:javascript复制
    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、如果两个if只要有一个有输出就会在sql中添加 where-->
        <where>
            <if test="name!=null and name!=''">
                <!--2、如果where后以and或or开始则会删除and或or-->
                and name like concat(concat('%',#{name}),'%');
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </where>

    </select>

测试:

代码语言:javascript复制
    /**
     * Method: selectStudentsByNameAndSex
     */
    @Test
    public void testSelectStudentsByNameAndSex() throws Exception {
        List<Student> students=dao.selectStudentsByNameAndSex("a",null);
        System.out.println(students);
        Assert.assertNotNull(students);
    }

 结果:

这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

where标记的作用类似于动态sql中的set标记,他的作用主要是用来简化sql语句中where条件判断的书写的,如下所示:

代码语言:javascript复制
  <select id="selectByParams" parameterType="map" resultType="user">
    select * from user
    <where>
      <if test="id != null ">id=#{id}</if>
      <if test="name != null and name.length()>0" >and name=#{name}</if>
      <if test="gender != null and gender.length()>0">and gender = #{gender}</if>
    </where>
  </select>   

在上述SQL中加入ID的值为null的话,那么打印出来的SQL为:select * from user where name="xx" and gender="xx"

where 标记会自动将其后第一个条件的and或者是or给忽略掉

2.4、if set设置值

当update语句中没有使用if标签时,如果有一个参数为null,都会导致错误。

当在update语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置SET 关键字,和剔除追加到条件末尾的任何不相关的逗号。如果set包含的内容为空的话则会出错。

使用if set标签修改后,如果某项为null则不进行更新,而是保持数据库原值。

如果通过if判断表面可以解决问题,如下所示:

代码语言:javascript复制
    <update id="updateStudent" parameterType="student">

        update student set  
        
        <if test="name!=null and name.lenght()>0">
            name=#{name} ,
        </if>

        <if test="sex!=null and sex.lenght()>0">
            sex=#{sex}
        </if>
        
        where id=#{id}
        
    </update>

这样做也会有问题,就是当sex为空时的sql就变成了 update student set name=#{name} , where id=#{id},这明显是错误的。

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

接口:

代码语言:javascript复制
    /**
     * 更新学生
     */
    int updateStudent(Student entity);

映射:

代码语言:javascript复制
    <update id="updateStudent" parameterType="student">
        update student
        <!--自动添加set-->
        <set>
            <!--智能处理逗号问题-->
            <if test="name!=null and name.length()>0">
                name=#{name}
            </if>

            <if test="sex!=null and sex.length()>0">
                sex=#{sex}
            </if>
        </set>
        where id=#{id}
    </update>

注意:某些情况下逗号必须添加,如下所示:

代码语言:javascript复制
    <update id="updateStudent" parameterType="student">
        update student
        <!--自动添加set-->
        <set>
            <!--智能处理逗号问题-->
            <if test="name!=null and name.length()>0">
                name=#{name} , 
            </if>

            <if test="sex!=null and sex.length()>0">
                sex=#{sex} , 
            </if>
        </set>
        where id=#{id}
    </update>

结尾的逗号会被自动删除。

测试:

代码语言:javascript复制
    /**
     * Method: updateStudent
     */
    @Test
    public void testUpdateStudent() throws Exception {
        //会将实体中的每一个字段都更新,不好
//        Student entity=dao.selectStudentById(11);
//        //entity.setName("张丽美");
//        entity.setSex("girl");
//
//        Assert.assertEquals(1,dao.updateStudent(entity));

        //不需要先执行查询
        Student student=new Student();
        student.setId(9);
        //只更新了name与sex没有关系
        student.setName("malili");
        Assert.assertEquals(1,dao.updateStudent(student));
    }

结果:

这样写,如果第一个条件 name 为空,那么 sql 语句为:update student set sex=? where id=?

如果第一个条件不为空,那么 sql 语句为:update student u set name= ? , sex = ? where id=?

set主要解决了自动添加标签与处理逗号的问题,另外这种更新方法比较以前的全部更新方式在开发中性能更高。

2.5、choose(when,otherwise) 开关

如果不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句。

假定这里需要优先根据编号搜索,没有时选择name,最后考虑sex:

接口:

代码语言:javascript复制
    /**
     * 根据学生编号、姓名和性别获得学生集合
     */
    List<Student> selectStudentsByNameAndSex(@Param("id") int id, @Param("name") String name,@Param("sex") String sex);

映射:

代码语言:javascript复制
    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student
        <where>
        <choose>
            <when test="id>0">
                id=#{id}
            </when>
            <when test="name!=null and name!=''">
                name=#{name}
            </when>
            <otherwise>
                sex=#{sex}
            </otherwise>
        </choose>
        </where>
    </select>

测试:

代码语言:javascript复制
    /**
     * Method: selectStudentsByNameAndSex
     */
    @Test
    public void testSelectStudentsByNameAndSex() throws Exception {
        List<Student> students=dao.selectStudentsByNameAndSex(1,"rose","girl");
        System.out.println(students);
        Assert.assertNotNull(students);
    }

结果:

也就是说,这里我们有三个条件,id,name,sex,只能选择一个作为查询条件

如果 id 不为空,那么查询语句为:select * from student where  id=?

如果 id 为空,那么看name是否为空,如果不为空,那么语句为 select * from student where  name=?;

如果name为空,那么查询语句为 select * from student  where sex=?

2.6、trim裁剪

trim标记是一个格式化的标记,可以完成set或者是where标记的功能

①、用 trim 改写上面第二点的 if where 语句

if where的办法:

代码语言:javascript复制
    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、如果两个if只要有一个有输出就会在sql中添加 where-->
        <where>
            <if test="name!=null and name!=''">
                <!--2、如果where后以and或or开始则会删除and或or-->
                and name like concat(concat('%',#{name}),'%');
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </where>

    </select>

trim的办法:

代码语言:javascript复制
    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、prefix表示将前置where,prefixOverrides将删除打头内容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select>

测试结果:

prefix:将加上前缀      

prefixoverride:去掉第一个and或者是or

②、用 trim 改写上面第三点的 if set 语句

if set的方法:

代码语言:javascript复制
    <update id="updateStudent" parameterType="student">
        update student
        <!--自动添加set-->
        <set>
            <!--智能处理逗号问题-->
            <if test="name!=null and name.length()>0">
                name=#{name}
            </if>

            <if test="sex!=null and sex.length()>0">
                sex=#{sex}
            </if>
        </set>
        where id=#{id}
    </update>

trim的方法:

代码语言:javascript复制
    <update id="updateStudent" parameterType="student">
        update student

           <trim prefix="set" suffixOverrides=",">
               <if test="name!=null and name.length()>0">
                   name=#{name},
               </if>

               <if test="sex!=null and sex.length()>0">
                   sex=#{sex},
               </if>
           </trim>

        where id=#{id}
    </update>

结果:

suffix:后缀  

suffixoverride:去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样) 

可以自定义添加前后缀,与之对应的属性是prefix和suffix。同时通过prefixOverrides和suffixOverrides分别来覆盖首尾部的内容,即忽略不必要的前后缀。就是说它可以充当where标签,也可以充当set标签啦~ 充当where标签:

代码语言:javascript复制
<trim prefix = "where" prefixOverrides="and|or" >
...
</trim>

充当set标签:

代码语言:javascript复制
<trim prefix="set" suffixOverrides=",">
...
</trim>

例子:动态添加用户属性

代码语言:javascript复制
<insert id="find" resultType="Admin">
insert into admin 
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test = "aname != null and aname !='' ">
aname,
</if>
<if test = "city != null and city !='' ">
city,
</if>
<if test = "age != null and age !='' ">
age,
</if>
</trim>
<trim prefix="values(" suffix=")" suffixOverrides=",">
<if test = "aname != null and aname !='' ">
#{aname},
</if>
<if test = "city != null and city !='' ">
#{city},
</if>
<if test = "age != null and age !='' ">
#{age},
</if>
</trim>
</insert>

上面相应的语句为:insert into admin (…) values(…);。通过trim标签用()包裹,以及自动忽略尾部的逗号。

2.7、SQL 片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

比如:下面的映射文件中对于id,name,sex出现多次:

代码语言:javascript复制
    <select id="selectStudentsByNameOrSex" resultType="student">
        SELECT id,name,sex from student where name like '%${realname}%' or sex=#{sex};
    </select>

    <select id="selectStudentsByIdOrSex" resultType="student">
        SELECT id,name,sex from student where id=#{no} or sex=#{sex};
    </select>

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、prefix表示将前置where,prefixOverrides将删除打头内容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select>

加粗的内容是重复的,通过sql片段复用。

定义sql片段:

代码语言:javascript复制
    <!--定义sql片段-->
    <sql id="col_student">
        id,name,sex
    </sql>
    

引用 sql 片段

代码语言:javascript复制
    <select id="selectStudentsByNameOrSex" resultType="student">
        <!--引用sql片段-->
        SELECT <include refid="col_student"></include> from student where name like '%${realname}%' or sex=#{sex};
    </select>

    <select id="selectStudentsByIdOrSex" resultType="student">
        SELECT <include refid="col_student"></include> from student where id=#{no} or sex=#{sex};
    </select>

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT <include refid="col_student"></include> from student

        <!--1、prefix表示将前置where,prefixOverrides将删除打头内容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select> 

结果:

注意:①、最好基于 单表来定义 sql 片段,提高片段的可重用性

   ②、在 sql 片段中不要包括 where 

sql片段带参数:

定义时使用参数:

代码语言:javascript复制
    <!--定义sql片段-->
    <sql id="col_student">
        ${alias}.id,${alias}.name,${alias}.sex
    </sql>

引用时指定参数:

代码语言:javascript复制
    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT

        <include refid="col_student">
            <property name="alias" value="s"></property>
        </include>

        from student s

        <!--1、prefix表示将前置where,prefixOverrides将删除打头内容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select>

结果:

2.8、foreach循环

foreach元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

注意 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

到此我们已经完成了涉及 XML 配置文件和 XML 映射文件的讨论。下一章将详细探讨 Java API,这样就能提高已创建的映射文件的利用效率。

foreach的主要用在构建in条件中,他可以迭代一个集合。foreach元素的属性主要有:item,index,collection,open,separator,close。

下面对属性进行简单的介绍:

item:表示集合中每一个元素进行迭代时的别名。

index:指定一个名字,用于表示在迭代过程中每次迭代的位置。

open:表示以什么开始。

separator:每次迭代以什么分割。

close:以什么关闭。

collection:最重要且必须指定的有三种情况:

1.如果传入的是单独参数的List类型时,collection的属性值为list。

2.如果传入的是单独参数的数组时,collection的属性值为array。

3.如果传入多个参数时,我们把多个参数放入map中,单参数也可以放入map中。map中的key就是参数名,所以collection属性值就是传入的List或者array对象在Map里的key。 

一、用 foreach 来改写 select * from user where id=1 or id=2 or id=3

代码语言:javascript复制
<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.zhangguo.User">
 select * from user
 <where>
 <!--
 collection:指定输入对象中的集合属性
 item:每次遍历生成的对象
 open:开始遍历时的拼接字符串
 close:结束时拼接的字符串
 separator:遍历对象之间需要拼接的字符串
 select * from user where 1=1 and (id=1 or id=2 or id=3)
 -->
 <foreach collection="ids" item="id" open="and (" close=")" separator="or">
 id=#{id}
 </foreach>
 </where>
</select>

二、我们用 foreach 来改写 select * from user where id in (1,2,3)

代码语言:javascript复制
<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.zhangguo.User">
 select * from user
 <where>
 <!--
 collection:指定输入对象中的集合属性
 item:每次遍历生成的对象
 open:开始遍历时的拼接字符串
 close:结束时拼接的字符串
 separator:遍历对象之间需要拼接的字符串
 select * from user where 1=1 and id in (1,2,3)
 -->
 <foreach collection="ids" item="id" open="and id in (" close=") " separator=",">
 #{id}
 </foreach>
 </where>
 </select> 

其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改。

参考:

代码语言:javascript复制
<?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">

<!-- namespace的名字需要跟接口的类名一致 -->
<mapper namespace="cn.bdqn.dao.UserMapper">
 <!-- 
 1、resultMap属性:type为java实体类;id为此resultMap的标识
 2、resultMap的子元素:
 id – 一般对应到数据库中该行的ID,设置此项可以提高Mybatis性能.
 result – 映射到JavaBean 的某个“简单类型”属性,String,int等.
 association – 映射到JavaBean 的某个“复杂类型”属性,其他JavaBean类.
 collection –复杂类型集合 
 -->
 
 <!--根据roleId获取用户列表: 当数据库中的字段信息与对象的属性不一致时需要通过resultMap来映射 -->
 <!-- <resultMap type="User" id="seachUserResult">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode"/>
 <result property="userName" column="userName"/>
 <result property="roleId" column="roleId"/>
 <result property="roleName" column="roleName"/>
 </resultMap>
 
 <select id="getUserListByRoleId" parameterType="Role" resultMap="seachUserResult">
 select u.*,r.roleName as roleName from user u,role r where u.roleId = r.id and u.roleId = #{id}
 </select> -->
 
 <!-- 根据roleId获取用户列表 association start-->
 <resultMap type="User" id="seachUserResult">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode" />
 <result property="userName" column="userName" />
 <result property="roleId" column="roleId" />
 <!-- <association property="role" javaType="Role" >
 <result property="id" column="id"/>
 <result property="roleCode" column="roleCode"/>
 <result property="roleName" column="roleName"/>
 </association> -->
 <association property="role" javaType="Role" resultMap="roleMap"/>
 </resultMap>
 
 <resultMap type="Role" id="roleMap">
 <result property="id" column="id"/> 
 <result property="roleCode" column="roleCode"/> 
 <result property="roleName" column="roleName"/> 
 </resultMap>
 
 <select id="getUserListByRoleId" parameterType="Role" resultMap="seachUserResult">
 select u.*,r.roleCode as roleCode,r.roleName as roleName from user u,role r where u.roleId = r.id and u.roleId = #{id}
 </select>
 
 <!-- association end-->
 
 <!-- 获取指定用户的地址列表(user表-address表:1对多关系) collection start-->
 <resultMap type="User" id="userMap">
 <id property="id" column="userId"/>
 <collection property="addressList" ofType="Address">
 <id property="id" column="a_id"/>
 <result property="postCode" column="postCode"/>
 <result property="addressContent" column="addressContent"/>
 </collection>
 </resultMap>
 
 <select id="getAddressListByUserId" parameterType="User" resultMap="userMap">
 select *,a.id as a_id from user u,address a where u.id=a.userId and u.id=#{id}
 </select>
 <!-- collection end -->
 
 <resultMap type="User" id="seachUser">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode"/>
 <result property="userName" column="userName"/>
 <result property="roleId" column="roleId"/>
 <result property="roleName" column="roleName"/>
 </resultMap>
 
 <!-- <select id="searchUserList" parameterType="User" resultMap="seachUser">
 select u.*,r.roleName as roleName from user u,role r where u.roleId = r.id
 and u.roleId = #{roleId}
 and u.userCode like CONCAT ('%',#{userCode},'%') //防止sql注入
 and u.userName like CONCAT ('%',#{userName},'%') 
 </select> -->
 
 <!-- 
 1、有些时候,sql语句where条件中,需要一些安全判断,例如按性别检索,如果传入的参数是空的,此时查询出的结果很可能是空的,也许我们需要参数为空时,是查出全部的信息。这是我们可以使用动态sql,增加一个判断,当参数不符合要求的时候,我们可以不去判断此查询条件。
 2、mybatis 的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类: 
 if 语句 (简单的条件判断) 
 choose (when,otherwize) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似.
 trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀) 
 where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误) 
 set (主要用于更新时) 
 foreach (在实现 mybatis in 语句查询时特别有用) 
 -->
 
 <!-- if(判断参数) - 将实体类不为空的属性作为where条件 -->
 <select id="searchUserList" parameterType="User" resultMap="seachUser">
 select u.*,r.roleName as roleName from user u,role r where u.roleId = r.id
 <if test="roleId!=null">
 and u.roleId = #{roleId}
 </if>
 <if test="userCode != null">
 and u.userCode like CONCAT ('%',#{userCode},'%') 
 </if>
 <if test="userName != null">
 and u.userName like CONCAT ('%',#{userName},'%') 
 </if>
 </select>
 
 
 
 
 
 <select id="count" resultType="int">
 select count(1) from user
 </select>
 
 <insert id="add" parameterType="User">
 insert into user (userCode,userName,userPassword) 
 values (#{userCode},#{userName},#{userPassword})
 </insert>
 
 <!-- if/set(判断参数) - 将实体类不为空的属性更新 --> 
 <!-- <update id="update" parameterType="User">
 update user 
 <set>
 <if test="userCode != null and userCode != ''">userCode=#{userCode},</if>
 <if test="userName != null">userName=#{userName},</if>
 <if test="userPassword != null">userPassword=#{userPassword},</if>
 <if test="roleId != null">roleId=#{roleId}</if>
 </set>
 where id=#{id}
 </update> -->
 
 <!-- if/trim代替set(判断参数) - 将实体类不为空的属性更新 --> 
 <update id="update" parameterType="User">
 update user 
 <trim prefix="set" suffixOverrides=",">
 <if test="userCode != null and userCode != ''">userCode=#{userCode},</if>
 <if test="userName != null">userName=#{userName},</if>
 <if test="userPassword != null">userPassword=#{userPassword},</if>
 <if test="roleId != null">roleId=#{roleId}</if>
 </trim>
 where id=#{id}
 </update>
 
 <!--注意: 你可以传递一个List实例或者数组作为参数对象传给MyBatis。
 当你这么做的时候,MyBatis会自动将它包装在一个Map中,用名称在作为键。
 List实例将会以“list”作为键,而数组实例将会以“array”作为键。
 配置文件中的parameterType是可以不配置的-->
 <resultMap type="User" id="userMapByDep">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode"/>
 <result property="userName" column="userName"/>
 </resultMap>
 <!-- foreach(循环array参数) - 作为where中in的条件 -->
 <select id="getUserByDepId_foreach_array" resultMap="userMapByDep">
 select * from user where depId in 
 <foreach collection="array" item="depIds" open="(" separator="," close=")">
 #{depIds}
 </foreach>
 </select>
 
 <!-- foreach(循环List<String>参数) - 作为where中in的条件 -->
 <select id="getUserByDepId_foreach_list" resultMap="userMapByDep">
 select * from user where depId in 
 <foreach collection="list" item="depIdList" open="(" separator="," close=")">
 #{depIdList}
 </foreach>
 </select>
 
 
 <delete id="delete" parameterType="User">
 delete from user where id=#{id}
 </delete>
 
 <select id="getUserList" resultType="User">
 select * from user
 </select>
</mapper>

假定我们要多删除:

接口:

代码语言:javascript复制
    /**
     * 删除多个学生通过编号
     */
    int deleteStudents(List<Integer> ids);

映射:

代码语言:javascript复制
    <delete id="deleteStudents">
        delete from student where id in
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

注意collection这里只能是list,不能是ids,因为反射时获取不到参数名称。

测试:

代码语言:javascript复制
    /**
     * Method: deleteStudents
     */
    @Test
    public void testDeleteStudents() throws Exception {
        List<Integer> ids=new ArrayList<Integer>();
        ids.add(8);
        ids.add(9);
        Assert.assertEquals(2,dao.deleteStudents(ids));
    }

结果:

2.9、bind 绑定变量

bind标签可以使用OGNL表达式创建一个变量并将其绑定到上下文中。

bind标签的两个属性都是不选项,name为绑定到上下文的变量名,value为OGNL表达式,创建一个bind标签后,就可以在下面直接使用了。 使用bind拼接字符串不仅可以避免因更换数据库而修改SQL,也能预防SQL注入。

代码语言:javascript复制
      <!-- List<Employee> getEmpsTestInnerParameter(Employee employee); -->
      <select id="getEmpsTestInnerParameter" resultType="com.hand.mybatis.bean.Employee">
          <!-- bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值 -->
          <bind name="bindeName" value="'%' eName '%'"/> eName是employee中一个属性值
          SELECT * FROM emp 
          <if test="_parameter!=null">
            where ename like #{bindeName}
          </if>
      </select>

三、视频

 https://www.bilibili.com/video/av32447485/

四、示例

https://git.coding.net/zhangguo5/MyBatis02.git

https://git.coding.net/zhangguo5/MyBatis03.git

五、作业

0、完成一个多删除功能,前端可以勾选任意条记录,后端实现多删除功能,删除前提示是否删除。

1、个人项目的数据库设计,个人项目的静态页面设计(2个,一个必须是首页,PC端)

2、重现本章示例

3、任务指导手册所有mybatis理论题

4、根据如下ER图创建4个表,完成1-1,1-N,M-N关系的查询,无需界面,测试通过即可

5、完成图书管理系统中二表关联,显示图书类型

代码语言:javascript复制
请实现一个简易图书管理系统(LibSystem),实现图书管理功能,要求如下:
1、管理数据库中所有图书(Books),包含图书编号(isbn)、书名(title)、作者(author)、价格(price)、出版日期(publishDate)
2、Maven多模块 MySQL Git MyBatis JUnit单元测试
3、表示层可以是AJAX或JSTL

请实现一个迷你图书管理系统(LibSystem),实现图书管理功能,要求如下:
1、管理数据库中所有图书分类(Categories),包含图书编号(id),名称(name)
2、管理数据库中所有图书(Books),包含图书编号(isbn)、类别(categoryId,外键)书名(title)、作者(author)、价格(price)、出版日期(publishDate)、封面(cover)、详细介绍(details)
3、分页
4、多条件组件查询(3个以上的条件)
5、上传封面
6、富文本编辑器

6、使用任意的原型开发工具设计出个人项目的1-2个界面,工具:Balsamiq Mockups,Axure RP Pro 7.0。

7、将动态sql中的所有知识点全部应用于作业4

1、重现上所有上课示例

2、请使用Maven多模块 Git MyBatis完成一个单表的管理功能,需要UI,可以AJAX也可以JSTL作表示层。

3、分页,多条件组合查询,多表连接(选作)

4、内部测试题(4个小时)

4.1、请实现一个简易图书管理系统(LibSystem),实现图书管理功能,要求如下:(初级)

1、管理数据库中所有图书(Books),包含图书编号(isbn)、书名(title)、作者(author)、价格(price)、出版日期(publishDate)

2、Maven多模块 MySQL Git MyBatis JUnit单元测试

3、表示层可以是AJAX或JSTL

C10 R(10 10) U10 D10

4.2、请实现一个迷你图书管理系统(LibSystem),实现图书管理功能,要求如下:(中级)

1、管理数据库中所有图书分类(Categories),包含图书编号(id),名称(name)

2、管理数据库中所有图书(Books),包含图书编号(isbn)、类别(categoryId,外键)书名(title)、作者(author)、价格(price)、出版日期(publishDate)、封面(cover)、详细介绍(details)

3、分页 10

4、多条件组件查询(3个以上的条件任意组合)(高级) 10

5、多删除 (高级) 10

6、上传封面 (高级) 10

7、富文本编辑器 (高级) 10

0 人点赞