0.软件框架技术简介

软件框架(software framework),通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范,也指为了实现某个软件组件规范时,提供规范所要求之基础功能的软件产品。
框架的功能类似于基础设施,与具体的软件应用无关,但是提供并实现最为基础的软件架构和体系。

  1. 为什么需要框架技术:
    1. 帮我们更快更好地构建程序
    2. 是一个应用程序的半成品
    3. 提供可重用的公共结构
    4. 按一定规则组织的一组组件
  2. 优势:
    1. 不用再考虑公共问题
    2. 专心在业务实现上
    3. 结构统一,易于学习、维护
    4. 新手也可写出好程序

      不要重复造轮子(Stop Trying to Reinvent the Wheel),已经成为开发人员的基本原则。

Java世界中的主流框架技术: Spring、SpringMVC、MyBatis、Struts、Hibernate、SpringBoot等。

1.MyBatis简介

1.1.ORM思想的提出

数据库中数据是以表的形式存在的,而java中使用的数据都是对象型的。所以不得不要将表数据转换成对象数据。 这样就会产生大量的没有技术含量的纯 “体力” 型代码。

  1. while(rs.next()) {
  2. Business business = new Business();
  3. business.setBusinessId(rs.getInt("businessId"));
  4. business.setPassword(rs.getString("password"));
  5. business.setBusinessName(rs.getString("businessName"));
  6. business.setBusinessAddress(rs.getString("businessAddress"));
  7. business.setBusinessExplain(rs.getString("businessExplain"));
  8. business.setStarPrice(rs.getDouble("starPrice"));
  9. business.setDeliveryPrice(rs.getDouble("deliveryPrice"));
  10. list.add(business);
  11. }

上面代码就属于纯 “体力” 型代码。这些代码工作量大、单调枯燥、占用大量开发时间。为了解决这个问题,出现了ORM思想。
ORM(对象-关系映射):完成对象数据到关系型数据映射的机制称为对象-关系映射。

1.2.MyBatis框架

MyBatis就是一个ORM框架。当然,也是一个持久层框架。
MyBatis封装了JDBC, 将数据库中的表数据自动封装到对象中。这样就可以以面向对象的方式操作数据了。它的出现,使得开发工作量变小了,可以将精力集中在业务逻辑的处理上。代码精简易读。
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。 也就是说:iBatis3.0之后都要改名为MyBatis 。
MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
实际上,MyBatis最核心的功能,就是实现了输入映射和输出映射

2.MyBatis实例

2.1.添加Mybatis依赖

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
</dependencies>

2.2.创建MyBatis配置文件

在 resources 文件夹中创建 SqlMapConfig.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置数据源相关属性和事务 -->
    <environments default="development">
        <!-- 可以配置多个数据源环境,默认使用default中的值 -->
        <environment id="development">
            <!-- 使用jdbc的事务管理 -->
            <transactionManager type="JDBC" />
            <!-- 配置数据源,并使用自带数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url"
                    value="jdbc:mysql://localhost:3306/emp?characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="123" />
            </dataSource>
        </environment>
    </environments>
    <!-- 配置映射文件,可配置多个 -->
    <mappers>
        <mapper resource="com/neusoft/po/Emp.xml" />
    </mappers>
</configuration>
  1. transactionManager标签的type属性有两种取值:
    1. JDBC:全部使用jdbc的事务管理
    2. MANAGED:不使用事务管理,也从不提交
  2. dataSource标签的type属性有三种取值:
    1. POOLED:使用Mybatis自带的数据库连接池
    2. UNPOOLED:不使用任何数据库连接池
    3. JNDI:jndi形式使用数据库连接

2.3.创建po类

创建 Emp.java类

package com.neusoft.po;
public class Emp {
    private Integer empno;
    private String ename;
    private String job;
    private String hiredate;
    private Double sal;
    private Integer deptno;
    @Override
    public String toString() {
        return empno+"\t"+ename+"\t"+job+"\t"+hiredate+"\t"+sal+"\t"+deptno;
    }
    //get/set方法...
}

注意:po类属性名必须与数据库表的字段名一致。

2.4.创建映射文件

在 po 类所在包下,创建相同名称的映射文件:Emp.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="emp">
    <select id="getEmpById" parameterType="int" resultType="com.neusoft.po.Emp">
        select * from emp where empno = #{empno}
    </select>
</mapper>

注意: mapper标签:映射文件的根标签。 mapper标签namespace属性:命名空间,对sql进行分类管理,可防止id重复 select标签:表示查询。 select标签id属性:此属性要唯一。这个id可称为statement的id。 select标签parameterType属性:sql参数的类型。 select标签resultType属性:sql语句执行后返回的类型。 sql语句:

{}: 表示sql参数,一个占位符。

当parameterType属性为对象类型时:#{} 中的参数名为对象的属性名。 当parameterType属性为单个值时,参数名可以任意。

从映射文件中可以看到:MyBatis会根据字段名与实体对象中的属性名进行映射,从而实现自动将查询数据注入到实体对象的每一个属性中。这就是输出映射。

2.5.测试

package com.neusoft;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.neusoft.po.Emp;
public class Test {
    public static void main(String[] args) {
        try {
            //读取配置文件,获得配置文件信息
            InputStream input = Resources.getResourceAsStream("SqlMapConfig.xml");
            //通过配置信息创建SqlSessionFactory
            SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(input);
            //通过SqlSessionFactory打开数据库会话
            SqlSession sqlSession = ssf.openSession();
            //通过SqlSession的selectOne(返回一行记录时使用)方法执行sql语句
            Emp emp = sqlSession.selectOne("emp.getEmpById",7521);
            System.out.println(emp);
            //关闭数据库会话
            sqlSession.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 当返回一条记录时,使用 SqlSession 的 selectOne 方法执行sql语句,MyBatis会将返回记录输出映射为一个java对象。
  2. 当返回多条记录时,使用 SqlSession 的 selectList 方法执行sql语句,MyBatis会将返回记录输出映射为一个java集合。
  3. selectOne 方法和 selectList 方法都只有两个参数:
    1. 第一个参数为指定的sql语句,根据映射文件中的id,注意要加上命名空间
    2. 第二个参数是给 sql语句参数传值(可以是一个值、或一个对象)

2.6.使用log4j输出日志

上面实例运行后,在控制台中只输出了查询结果,却没有中间的执行过程信息。这对我们开发时进行错误调试是非常不利的。我们希望在开发时,能够看到详细的执行过程
MyBatis默认使用log4j输出日志。所以,下面使用log4j来让MyBatis输出更多的日志信息。

  1. 添加log4j依赖

    <!-- 使用log4j输出更多的日志信息 -->
    <dependency>
     <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
    </dependency>
    
  2. 在resources文件夹中添加log4j配置文件(log4j.properties)

    ### direct log messages to stdout ###
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.out
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
    ### direct messages to file mylog.log ###
    ### log4j.appender.file=org.apache.log4j.FileAppender
    ### log4j.appender.file.File=d:mylog.log
    ### log4j.appender.file.layout=org.apache.log4j.PatternLayout
    ### log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
    ### set log levels - for more verbose logging change 'debug?info?warn?error' ###
    ### log4j.rootLogger=debug,stdout,file
    log4j.rootLogger=debug,stdout
    

    3.MyBatis核心接口和类

  3. SqlSessionFactoryBuilder负责构建SqlSessionFactory,并且提供了多个build()方法的重载。也就是说:此对象可以从xml配置文件,或从Configuration对象来构建SqlSessionFactory。

  4. SqlSessionFactory就是创建SqlSession实例的工厂。通过openSession方法来获取SqlSession对象。而且,SqlSessionFactory一旦被创建,那么在整个应用程序期间都存在。
  5. SqlSession是一个面向程序员的接口,它提供了面向数据库执行sql命令所需的所有方法。SqlSession对应一次数据库会话,它是线程不安全的。

    4.封装持久层

    MyBatis开发DAO层有两种方式:

  6. 原始dao方式

  7. mapper代理方式

    4.1.原始dao方式

    按照JDBC课程中封装dao层的方式,我们可以先封装一个 Util 工具类,在此工具类中封装一个获取SqlSessionFactory的方法。然后创建dao接口和实现类。
    SqlSessionFactory工具类:

    package com.neusoft.util;
    import java.io.IOException;
    import java.io.Reader;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    public class Util {
     public static SqlSessionFactory sqlSessionFactory = null;
     public static SqlSessionFactory getSqlSessionFactory() {
         if(sqlSessionFactory==null){
             String resource = "mybatis/SqlMapConfig.xml";
             try {
                 Reader reader = Resources.getResourceAsReader(resource);
                 sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
         return sqlSessionFactory;
     }
    }
    

    dao接口:

    package com.neusoft.dao;
    import java.util.List;
    import com.neusoft.po.Emp;
    public interface EmpDao {
     public Emp getEmpById(int empno);
     public List<Emp> listEmp();
    }
    

    dao实现类:

    package com.neusoft.dao.impl;
    import java.util.List;
    import org.apache.ibatis.session.SqlSession;
    import com.neusoft.dao.EmpDao;
    import com.neusoft.po.Emp;
    import com.neusoft.util.Util;
    public class EmpDaoImpl implements EmpDao{
     @Override
     public Emp getEmpById(int empno){
         SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
         Emp emp = sqlSession.selectOne("emp.getEmpById",empno);
         sqlSession.close();
         return emp;
     }
     @Override
     public List<Emp> listEmp(){
         SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
         List<Emp> list = sqlSession.selectList("emp.listEmp");
         sqlSession.close();
         return list;
     }
    }
    

    测试:

    EmpDao dao = new EmpDaoImpl();
    Emp emp = dao.getEmpById(7369);
    System.out.println(emp);
    List<Emp> list = dao.listEmp();
    for(Emp emp : list) {
     System.out.println(emp);
    }
    

    从上面代码中可以发现,使用原始dao方式存在很多问题:

  8. dao实现类中存在大量重复代码

  9. 调用sqlSession方法时,将statement的id硬编码了
  10. 调用sqlSession方法时传入的参数,由于sqlSession使用了泛型,所以即使传入参数的数据类型错误,在编译阶段也不会报错。

    4.2.mapper代理方式

    程序员只需要mapper接口和mapper.xml映射文件,Mybatis可以自动生成mapper接口实现类代理对象。程序员编写mapper接口需要遵循一些开发规范。
    mapper代理方式开发规范:

  11. 映射文件中的 namespace 必须是 mapper 接口的地址。

  12. 映射文件中 statement 的 id 必须与 mapper 接口中的方法名一致。
  13. 映射文件中 parameterType 必须与 mapper 接口中的方法参数类型一致。
  14. 映射文件中 resultType 必须与 mapper 接口中的返回值类型一致。(实际上,代理对象就是根据返回值类型来判断是使用selectOne方法还是selectList方法)

    4.2.1.创建mapper接口

    package com.neusoft.mapper;
    import java.util.List;
    import com.neusoft.po.Emp;
    public interface EmpMapper {
     public Emp getEmpById(int empno);
     public List<Emp> listEmpAll();
    }
    

    4.2.2.创建mapper映射文件

    注意:mapper接口与映射文件最好在同一个文件夹内,并且接口名与映射文件名要一致

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.neusoft.mapper.EmpMapper">
    <select id="getEmpById" parameterType="int" resultType="Emp">
        select * from emp where empno = #{empno}
    </select>
    <select id="listEmpAll" resultType="Emp">
        select * from emp order by empno
    </select>
</mapper>

4.2.3.注册mapper映射文件

<configuration>
    <mappers>
        <mapper resource="com/neusoft/mapper/EmpMapper.xml" />
    </mappers>
</configuration>

4.2.4.获取代理对象测试

SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpById(7369);
System.out.println(emp);
List<Emp> list = mapper.listEmpAll();
for(Emp e : list) {
    System.out.println(e);
}

5.MyBatis配置的优化

5.1.批量加载映射文件

如果映射文件与mapper接口名称一致,且处在同一个文件夹内,那么就可以使用接口来批量加载映射文件。

<configuration>
    <mappers>
        <package name="com.neusoft.mapper"/>
    </mappers>
</configuration>

自动加载com.neusoft.mapper包下,所有与接口名称一致的映射文件。(package标签可写多个)

5.2.批量定义类别名

在MyBatis中的配置文件中, parameterType和resultType都需要指定自定义类的全路径。类的全路径一般都很长,所以需要进行优化。也就是给类定义别名。

<configuration>
    <typeAliases>
        <package name="com.neusoft.po"/>
    </typeAliases>
</configuration>

自动扫描指定包中的类,并自动为这些类定义别名。定义的别名就是类名,而且首字母大写小写均可。

5.3.数据库配置优化

创建db.properties数据库配置文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/emp?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123

在MyBatis配置文件中使用db.properties数据库配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 加载数据库配置文件 -->
    <properties resource="db.properties"></properties>
    <!-- 定义类别名 -->
    <typeAliases>
        <package name="com.neusoft.po"/>
    </typeAliases>
    <!-- 配置数据源相关属性和事务 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>
    <!-- 加载映射文件 -->
    <mappers>
        <package name="com.neusoft.mapper"/>
    </mappers>
</configuration>

将有关数据库的配置分离出去,有利于分类管理。

6.常用数据库操作

6.1.多条件查询

<select id="listEmp" parameterType="Emp" resultType="Emp">
    select * 
    from emp 
    where job like concat('%',#{job},'%') and deptno=#{deptno} 
    order by empno
</select>
Emp emp = new Emp();
emp.setJob("经");
emp.setDeptno(20);
List<Emp> list = mapper.listEmp(emp);
for(Emp e : list) {
    System.out.println(e);
}

注意:

  1. parameterType只有一个。所以,有多个参数时使用对象传值(这就是输入映射)。
  2. {} 中书写的是实体对象的属性名,所以要严格区分大小写。

6.2.转义字符查询

由于 <(小于号)是标签关键词,因此不能识别小于号。所以MyBatis中设计了一些转义字符,来代替一些特殊字符:
MyBatis框架 - 图1

<select id="listEmpBySal" parameterType="double" resultType="Emp">
    select * from emp where sal &lt; #{sal} order by empno
</select>
List<Emp> list = mapper.listEmpBySal(2000.0);
for(Emp e : list) {
    System.out.println(e);
}

6.3.返回单值查询

<select id="listEmpCount" resultType="int">
    select count(*) from emp
</select>
int count = mapper.listEmpCount();
System.out.println(count);

注意:只有返回一行一列,resultType才能使用基本数据类型

6.4.插入(不获取主键)

<insert id="insertEmp1" parameterType="Emp">
    insert into emp(ename,job,hiredate,deptno)
    values(#{ename},#{job},#{hiredate},#{deptno})
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp1(emp);
sqlSession.commit();                  //注意:要commit提交
System.out.println(result);

注意:增删改都会返回int值,表示影响的行数。但是,insert标签中不能书写resultType属性

6.5.插入(获取主键)

<insert id="insertEmp2" parameterType="Emp">
    <selectKey keyProperty="empno" resultType="int" order="AFTER">
        select LAST_INSERT_ID()
    </selectKey>
    insert into emp(ename,job,hiredate,deptno)
    values(#{ename},#{job},#{hiredate},#{deptno})
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp2(emp);
sqlSession.commit();
System.out.println(result);
System.out.println(emp.getEmpno());      //获取返回的主键

注意:

  1. selectKey标签中的 select LAST_INSERT_ID() 语句就能获取生成的主键
  2. selectKey标签中的keyProperty属性就是主键名,MyBatis会自动将获取的主键封装给此属性。
  3. order的值有两种:BEFORE、AFTER
    BEFORE:先获取主键,然后执行insert; 比如 Oracle数据库。
    AFTER:先执行insert,然后获取主键; 比如 MySql数据库。

6.6.插入(获取主键)

<insert id="insertEmp3" parameterType="Emp" useGeneratedKeys="true" keyProperty="empno">
    insert into emp(ename,job,hiredate,deptno)
    values(#{ename},#{job},#{hiredate},#{deptno})
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp3(emp);
sqlSession.commit();
System.out.println(result);
System.out.println(emp.getEmpno());      //获取返回的主键

useGeneratedKeys设置为true后,mybatis会使用JDBC的getGeneratedkeys方法获取由数据库内部自动生成的主键,并将该值赋值给由keyProperty指定的属性; 注意:此种方式只适合于有自增长列的数据库(mysql、sqlserver等)

6.7.修改

<update id="updateEmp" parameterType="Emp">
    update emp set job=#{job},sal=#{sal} where empno=#{empno}
</update>
Emp emp = new Emp();
emp.setEmpno(7934);
emp.setJob("经理");
emp.setSal(2000.0);
int result = mapper.updateEmp(emp);
sqlSession.commit();
System.out.println(result);

注意:增删改都会返回int值,表示影响的行数。但是,insert标签中不能书写resultType属性

6.8.删除

<delete id="deleteEmp" parameterType="int">
    delete from emp where empno=#{empno}
</delete>
int result = mapper.deleteEmp(7939);
sqlSession.commit();
System.out.println(result);

7.动态sql

动态sql主要用于解决查询条件不确定的情况。也就是说:在实际开发中,经常需要根据用户是否输入了某个值,来确定是否需要这个条件。 MyBatis中用于动态sql的元素主要有:if、where、trim、set、foreach、choose等

7.1.if+where标签

<select id="listEmp" parameterType="Emp" resultType="Emp">
    select * from emp 
    <where>
        <if test="job!=null and job!=''">
            and job like concat('%',#{job},'%')
        </if>
        <if test="deptno!=null and deptno!=''">
            and deptno = #{deptno}
        </if>
    </where>
    order by empno
</select>
Emp emp = new Emp();
//emp.setJob("经");
//emp.setDeptno(10);        //注意:deptno属性必须为Integer类型
List<Emp> list = mapper.listEmp(emp);
for(Emp e : list) {
    System.out.println(e);
}

if+where会实现以下功能:

  1. 自动添加where
  2. 不需要考虑where后是否加and,mybatis会自动处理
  3. 不需要考虑是否加空格,mybatis会自动处理
  4. 没有 else 标签,也没有 else if 标签。

注意: job!=’’ 此处只可以判断是否为空,不能判断是否为某个值。也就是说:job!=’经理’ 是不好使的。

7.2.choose标签

<select id="listEmp" parameterType="Emp" resultType="Emp">
    select * from emp 
    <where>
        <choose>
            <when test="job!=null and job!=''">
                and job like concat('%',#{job},'%')
            </when>
            <when test="deptno!=null and deptno!=''">
                and deptno = #{deptno}
            </when>
            <otherwise></otherwise>
        </choose>
    </where>
    order by empno
</select>
Emp emp = new Emp();
emp.setJob("职员");
emp.setDeptno(10);
List<Emp> list = mapper.listEmp(emp);
for(Emp e : list) {
    System.out.println(e);
}

choose会实现如下功能:

  1. 多个 when 标签中,只能执行一个。也就是说:当一个 when 条件满足并执行后,其它的 when 将不再执行。
  2. 当所有 when 都不满足条件时,执行 otherwise 标签。

if 与 choose 的区别:if 相当于java中的if语句; choose相当于java中的switch语句。

7.3.trim标签

trim标签可以在自己包含的内容中加上某些前缀或后缀,与之对应的属性是:prefix、suffix。 还可以把包含内容的开始内容覆盖,即忽略。也可以把结束的某些内容覆盖,对应的属性是:prefixOverrides、suffixOverrides

<insert id="insertEmp1" parameterType="Emp">
    insert into emp
    <trim prefix="(" suffix=")" suffixOverrides=",">
        ename,deptno,
        <if test="job!=null and job!=''">
            job,
        </if>
        <if test="hiredate!=null and hiredate!=''">
            hiredate,
        </if>
    </trim>
    <trim prefix="values(" suffix=")" suffixOverrides=",">
        #{ename},#{deptno},
        <if test="job!=null and job!=''">
            #{job},
        </if>
        <if test="hiredate!=null and hiredate!=''">
            #{hiredate},
        </if>
    </trim>
</insert>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = new Emp();
emp.setEname("张三");
emp.setJob("职员");
//emp.setHiredate("2020-09-04");
emp.setDeptno(10);
int result = mapper.insertEmp1(emp);
sqlSession.commit();
System.out.println(result);

注意:

  1. prefix与suffix可以在sql语句中拼接出一对小括号。
  2. suffixOberrides可以将最后一个逗号去掉。

7.4.set标签

set标签主要用于更新操作时使用

<update id="updateEmp" parameterType="Emp">
    update emp 
    <set>
        <if test="job!=null and job!=''">
            job=#{job},
        </if>
        <if test="sal!=null and sal!=''">
            sal=#{sal},
        </if>
    </set>
    where empno=#{empno}
</update>
Emp emp = new Emp();
emp.setEmpno(7943);
//emp.setJob("职员1");
emp.setSal(3000.0);
int result = mapper.updateEmp(emp);
sqlSession.commit();
System.out.println(result);

注意:

  1. set可以在sql语句中自动加上set关键词。
  2. 可以自动将最后的逗号去掉。
  3. 上面写法中,必须要保证有一个if成立

7.5.foreach标签

foreach标签可以在sql中迭代一个集合或数组,主要用于拼接in条件。下面是一个批量删除示例:

<delete id="deleteEmp" parameterType="int">
    delete from emp where empno in
    <foreach collection="array" item="empno" open="(" close=")" separator=",">
        #{empno}
    </foreach>
</delete>
int[] arr = {7941,7942,7943};
int result = mapper.deleteEmp(arr);    //注意:此接口参数应为int数组
sqlSession.commit();
System.out.println(result);

foreach标签的属性:

  1. collection:需要遍历的类型,值有:list、array
  2. item:表示遍历出来的对象
  3. open:表示语句的开始部分
  4. close:表示语句的结束部分
  5. separator:表示每次迭代之间以什么符号为间隔
  6. index:每次迭代的位置索引,就是循环变量

8.MyBatis的输入映射和输出映射

8.1.输入映射总结

当sql语句需要一个参数时:

  1. 接口方法参数为一个基本数据类型;parameterType配置一个基本数据类型;

当sql语句需要多个参数时:

  1. 接口方法参数为一个实体对象类型;parameterType配置一个实体对象类型;
  2. 接口方法参数为一个集合类型(List、Map);parameterType配置集合中元素的类型;

    注意:当sql语句中需要判断一个基本数据类型的值是否为空时:

    1. 值的类型必须为包装类。
    2. 即使是只传一个基本数据类型,也要使用实体对象传值。 因为:如果在parameterType中设置Integer类型,那么Mybatis会自动寻找get方法来获取对象属性值。因此会出现没有get方法异常。

8.2.输出映射总结

当sql语句中的字段名与实体对象中的属性名一致时,使用resultType:

  1. 返回一条记录时,resultType可以配置成对象类型。
  2. 返回多条记录时,resultType也要配置成对象类型(表示集合中存储的对象)。
  3. 返回一条记录,且只有一列时,resultType可以配置成简单数据类型。

当sql语句中的字段名与实体对象中的属性名不一致时,使用resultMap:

  1. 在resultMap中,显式的书写sql字段名与实体对象属性名的映射

    8.3.resultMap的使用

    当sql语句中的字段名与实体对象中的字段名不一致时,可以使用resultMap来显式的进行映射。
    <!-- 定义一个resultMap,取一个唯一标识id -->
    <resultMap type="Emp" id="empResultMap">
     <id property="empno" column="empno_0"/>
     <result property="ename" column="ename_0"/>
     <result property="job" column="job_0"/>
     <result property="hiredate" column="hiredate_0"/>
     <result property="sal" column="sal_0"/>
     <result property="deptno" column="deptno_0"/>
    </resultMap>
    <!-- 使用resultMap属性,指明使用哪一个resultMap -->
    <select id="listEmpAll" resultMap="empResultMap">
     select empno empno_0,
            ename ename_0,
            job job_0,
            hiredate hiredate_0,
            sal sal_0,
            deptno deptno_0
     from emp 
     order by empno_0
    </select>
    
    List<Emp> list = mapper.listEmpAll();
    for(Emp e : list) {
     System.out.println(e);
    }
    

    注意:

    1. resultMap标签中的 id 属性:是此resultMap的唯一标识。
    2. resultMap标签中的 type 属性:是此resultMap 映射的实体对象。
    3. resultMap标签中的子标签id,用来配置主键的映射;子标签 result 用来配置其它字段的映射。
    4. 子标签id 和子标签 result 中的 property 属性,对应实体类中的属性,column 属性对应sql语句中的字段。

9.MyBatis关联查询

我们知道,在数据库中,表关系有:一对一、多对一、一对多、多对多。 那么在实际开发中,很多查询都是通过多个表之间的关系,进行多表连接查询的。
那么,在多表连接查询中,MyBatis如何实现输出映射呢,这就要使用MyBatis的关联查询。
MyBatis关联查询有两种:

  1. 多表连接形式
  2. 单独查询形式

    9.1.多表连接形式

    9.1.1.多对一关联查询

    9.1.1.1.修改实体类

    MyBatis框架 - 图2

    Dept是one的一方,Emp是many的一方; 在many的一方添加one的一方的对象,这就配置了实体类之间的多对一关联。

9.1.1.2.使用resultMap映射关联

<resultMap type="Emp" id="empResultMap">
    <id property="empno" column="empno"/>
    <result property="ename" column="ename"/>
    <result property="job" column="job"/>
    <result property="hiredate" column="hiredate"/>
    <result property="sal" column="sal"/>
    <result property="deptno" column="deptno"/>
    <association property="dept" javaType="Dept">
        <id property="deptno" column="ddeptno"/>
        <result property="dname" column="ddname"/>
        <result property="loc" column="dloc"/>
    </association>
</resultMap>
<select id="getEmpById" parameterType="int" resultMap="empResultMap">
    select e.*,
           d.deptno ddeptno,
           d.dname ddname,
           d.loc dloc
    from emp e left join dept d
         on e.deptno=d.deptno
    where e.empno = #{empno}
</select>
<select id="listEmpAll" resultMap="empResultMap">
    select e.*,
           d.deptno ddeptno,
           d.dname ddname,
           d.loc dloc
    from emp e left join dept d
         on e.deptno=d.deptno
</select>
SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpById(7369);
System.out.println(emp);
System.out.println(emp.getDept());
List<Emp> list = mapper.listEmpAll();
for(Emp e : list) {
    System.out.println(e);
    System.out.println(e.getDept());
}

association表示配置单个对象的关联映射 :

  1. association标签中的property:many的一方的实体类中,添加的属性名。
  2. association标签中的javaType:many的一方的实体类中,添加的属性类型。
  3. id标签和result标签中的property:one的一方的实体类中的属性名。
  4. id标签和result标签中的column:one的一方的,查询出来的字段名。

9.1.2.一对多关联查询

9.1.2.1.修改实体类

MyBatis框架 - 图3

Dept是one的一方,Emp是many的一方; 在one的一方添加many的一方的集合,这就配置了实体类之间的一对多关联。

9.1.2.2.使用resultMap映射关联

<resultMap type="Dept" id="deptResultMap">
    <id property="deptno" column="deptno"/>
    <result property="dname" column="dname"/>
    <result property="loc" column="loc"/>
    <collection property="empList" ofType="Emp">
        <id property="empno" column="eempno"/>
           <result property="ename" column="eename"/>
        <result property="job" column="ejob"/>
        <result property="hiredate" column="ehiredate"/>
        <result property="sal" column="esal"/>
        <result property="deptno" column="edeptno"/>
    </collection>
</resultMap>
<select id="getDeptById" parameterType="int" resultMap="deptResultMap">
    select d.*,
           e.empno eempno,
           e.ename eename,
           e.job ejob,
           e.hiredate ehiredate,
           e.sal esal,
           e.deptno edeptno
    from dept d left join emp e
         on d.deptno=e.deptno
    where d.deptno = #{deptno}
</select>
<select id="listDeptAll" resultMap="deptResultMap">
    select d.*,
           e.empno eempno,
           e.ename eename,
           e.job ejob,
           e.hiredate ehiredate,
           e.sal esal,
           e.deptno edeptno
    from dept d left join emp e
         on d.deptno=e.deptno
</select>
Dept dept = mapper.getDeptById(10);
System.out.println(dept);
List<Emp> empList = dept.getEmpList();
for(Emp e : empList) {
    System.out.println(e);
}
List<Dept> deptList = mapper.listDeptAll();
for(Dept d : deptList) {
    System.out.println(d);
    List<Emp> el = d.getEmpList();
    for(Emp e : el) {
        System.out.println(e);
    }
}

collection表示配置多个对象的集合的关联映射 :

  1. collection标签中的property:one的一方的实体类中,添加的集合属性名。
  2. collection标签中的ofType:one的一方的实体类中,添加的集合中的元素类型。
  3. id标签和result标签中的property:many的一方的实体类中的属性名。
  4. id标签和result标签中的column: many的一方的,查询出来的字段名。

9.2.单独查询形式

9.2.1.多对一关联查询

9.2.1.1.先在one的一方添加关联查询

<select id="getDeptByIdLazy" parameterType="int" resultType="Dept">
    select * from dept where deptno = #{deptno}
</select>

注意:此查询可以不在接口中书写响应的方法

9.2.1.2.many的一方添加查询

<resultMap type="Emp" id="empResultMap">
    <id property="empno" column="empno"/>
    <result property="ename" column="ename"/>
    <result property="job" column="job"/>
    <result property="hiredate" column="hiredate"/>
    <result property="sal" column="sal"/>
    <result property="deptno" column="deptno"/>
    <association property="dept" javaType="Dept" 
           select="com.neusoft.mapper.DeptMapper.getDeptByIdLazy" column="deptno" />
</resultMap>
<select id="getEmpById" parameterType="int" resultMap="empResultMap">
    select * from emp where empno = #{empno}
</select>

9.2.1.3.测试

SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpById(7369);
//System.out.println(emp);
//System.out.println(emp.getDept());

测试时,可以在日志中看到,执行了两次sql语句。 MyBatis框架 - 图4

9.2.2.一对多关联查询

9.2.2.1.先在many的一方添加关联查询

<select id="getEmpByIdLazy" parameterType="int" resultType="Emp">
    select * from emp where deptno = #{deptno}
</select>

注意:此查询可以不在接口中书写响应的方法

9.2.2.2.one的一方添加查询

<resultMap type="Dept" id="deptResultMap">
    <id property="deptno" column="deptno"/>
    <result property="dname" column="dname"/>
    <result property="loc" column="loc"/>
    <collection property="empList" ofType="Emp"
           select="com.neusoft.mapper.EmpMapper.getEmpByIdLazy" column="deptno"/>
</resultMap>
<select id="getDeptById" parameterType="int" resultMap="deptResultMap">
    select * from dept where deptno = #{deptno}
</select>

9.2.2.3.测试

SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.getDeptById(10);
System.out.println(dept);
List<Emp> list = dept.getEmpList();
for(Emp emp : list) {
    System.out.println(emp);
}

测试时,可以在日志中看到,执行了两次sql语句。 MyBatis框架 - 图5

9.3.关联映射总结

使用resultMap实现关联映射时:

  1. 使用association标签完成多对一或一对一映射。
    1. association标签:将关联查询信息映射到一个po对象中。
    2. association标签中的javaType属性:表示该po对象的类型。
    3. association标签中的select属性:表示应用哪一个关联查询。
    4. association标签中的column属性:表示应用关联查询的条件。
  2. 使用collection标签完成一对多,多对多映射。
    1. collection标签:将关联查询信息映射到一个list集合中。
    2. collection标签的ofType属性:表示该集合中的元素对象的类型。
    3. collection标签中的select属性:表示应用哪一个关联查询。
    4. collection标签中的column属性:表示应用关联查询的条件。

      9.4.关联查询的延迟加载

      延迟加载:执行查询时,关联查询不会立即加载。只有在使用关联数据时才会加载。 优点:按需加载,提高效率。

      注意: 要使用关联查询的延迟加载,就必须要使用单独查询形式。并且,需要先启用MyBatis的延迟加载配置(需要配置两项):

      1. lazyLoadingEnabled:延迟加载的全局开关(默认为false)。
      2. aggressiveLazyLoading:延迟加载整个对象(默认为true; false:对象的每个属性都会延迟加载,即属性按需加载)
<!-- 如果aggressiveLazyLoading为true,那么lazyLoadingEnabled即使为true也无效。 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

重新测试上面的关联查询(要测试单独查询形式): 当不需要使用关联数据时,关联查询将不会执行。

SqlSession sqlSession = Util.getSqlSessionFactory().openSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept = mapper.getDeptById(10);

MyBatis框架 - 图6

10.使用注解实现MyBatis映射

MyBatis也支持使用注解来配置映射语句。 主要有四种注解来实现增删改查:@Select、@Insert、@Update、@Delete

package com.neusoft.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import com.neusoft.po.Emp;
public interface EmpMapper {
    @Select("select * from emp where empno = #{empno}")
    public Emp getEmpById(int empno);
    @Select("select * from emp order by empno")
    public List<Emp> listEmpAll();
}

注意:

  1. 在映射文件中使用的所有的CRUD操作,都可以使用注解的形式完成。
  2. 当使用基于注解的映射器接口时,就不再需要映射配置文件了。
  3. 在实际开发中,可以单独使用映射文件,也可以单独使用注解,也可以混合使用。


本章作业

  1. Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
  2. Mapper接口的工作原理是什么?
  3. Mapper接口中的方法能重载吗?
  4. Mybatis动态sql是做什么的?
  5. Mybatis是否支持延迟加载?
  6. Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
  7. 简述动态sql的执行原理?
  8. 编程题:有如下数据库,其中包含两个表,请使用MyBatis完成对两张表的增删改查操作(要使用关联查询)。

goodsType商品分类表:

No 字段名 数据类型 size 默认値 约束 说明
1 gtId int PK、AI、NN 商品分类编号
2 gtName varchar 30 NN 商品分类名称
3 gtExplain varchar 60 商品分类介绍

goods商品表:

No 字段名 数据类型 size 默认値 约束 说明
1 goodsId int PK、AI、NN 商品编号
2 goodsName varchar 30 NN 商品名称
3 goodsExplain varchar 60 商品介绍
4 goodsPrice int NN 商品价格
5 gtId int NN 所属商品分类编号
SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
  `goodsId` int(11) NOT NULL AUTO_INCREMENT,
  `goodsName` varchar(30) NOT NULL,
  `goodsExplain` varchar(60) DEFAULT NULL,
  `goodsPrice` int(11) NOT NULL,
  `gtId` int(11) NOT NULL,
  PRIMARY KEY (`goodsId`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `goodstype`;
CREATE TABLE `goodstype` (
  `gtId` int(11) NOT NULL AUTO_INCREMENT,
  `gtName` varchar(30) NOT NULL,
  `gtExplain` varchar(60) DEFAULT NULL,
  PRIMARY KEY (`gtId`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `goods` VALUES ('1', '小米11 Ultra', '+199元得价值499元80W无线充套装', '5499', '1');
INSERT INTO `goods` VALUES ('2', '小米11 青春版', '全版本直降200元', '2099', '1');
INSERT INTO `goods` VALUES ('3', '小米10', '买赠129元冰封散热背夹', '3399', '1');
INSERT INTO `goods` VALUES ('4', 'Redmi Note 10 Pro', '付款前1000名赠价值99元小米定制T恤', '1699', '2');
INSERT INTO `goods` VALUES ('5', 'Redmi K40 Pro', '高考生认证立减100元', '2799', '2');
INSERT INTO `goods` VALUES ('6', 'Redmi 9', '购机赠小米移动流量卡', '799', '2');
INSERT INTO `goods` VALUES ('7', 'Redmi K40 游戏增强版', '购机前1000名赠小米定制T恤', '1999', '3');
INSERT INTO `goodstype` VALUES ('1', '小米手机', '小米手机类型的商品');
INSERT INTO `goodstype` VALUES ('2', 'Redmi手机', 'Redmi手机类型的商品');
INSERT INTO `goodstype` VALUES ('3', '游戏手机', '游戏手机类型的商品');