MyBatis

1.MyBatis简介

什么是Mybatis?

  • MyBatis是一款可以定义SQL、存储过程和高级映射的持久层框架,用于简化JDBC的开发
  • MyBatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高
  • MyBatis可以使用XML或注解配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集

持久层

  • 负责将数据到保存到数据库的那一层代码
  • JavaEE三层架构:表现层、业务层、持久层

框架

  • 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
  • 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展

2.搭建MyBatis

2.1创建Maven工程,添加依赖:

<dependencies>
    <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
    <!--MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
    <!--junit单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    <!-- 添加slf4j日志api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.20</version>
        </dependency>
        <!-- 添加logback-classic依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- 添加logback-core依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>

使用logback时需要导入一个logback.xml文件到项目的resource文件夹下。


2.2MyBatis核心配置文件

习惯上命名为mybatis-config.xml

<!-- XML头部的声明,它用来验证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>
  <!--environment环境配置-->
  <!--加载类路径下的属性文件-->
  <properties resource="db.properties"/>
  <environments default="development">
    <environment id="development">
      <!--事务管理器-->
      <transactionManager type="JDBC"/>
      <!--数据库连接相关配置,db.properties文件中的内容-->
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <!--引入mybatis映射文件-->
  <mappers>
    <mapper resource="Mapper/BookMapper.xml"/>
    <mapper class="Dao.BookMapper"/>
  </mappers>
</configuration>
核心配置文件详解
  • configuration(配置)

    • properties(属性)

    • settings(设置)

      • 下划线转驼峰

        <settings>
            <!-- 下划线 自动映射 驼峰 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
        
      • 延迟加载

        • 如果某个查询不想使用懒加载,则在association和collection标签当中设置fetchType=“eager”(立即加载)即可
        • 节省内存
        <settings>
            <!-- 延迟加载
                LazyLoadingEnabled: true,开启懒加载,所有关联对象都会延迟加载
                aggressiveLazyLoading: false, 开启时,任何方法的调用都会加载该对象的所有属性,否则,每个属性会按需加载--默认为false不开启,即按需加载
            -->
            <setting name="lazyLoadingEnabled" value="true"/>
            <setting name="aggressiveLazyLoading" value="false"/>
        </settings>
        
    • typeAliases(类型别名)

      类型别名,在Mapper的resultType属性中可以使用简单类型别名

      <configuration>
          <!-- 别名 -->
          <typeAliases>
              <typeAlias type="com.atguigu.mybatis.pojo.User" alias="user"></typeAlias>       
              <!-- 也可以指定一个包下面的别名, 且不区分大小写, 跟上方 typeLias 不能同时使用 -->
              <package name="com.atguigu.mybatis.pojo"></package>
          </typeAliases>
      </configuration>
      

      在Mapper.xml文件中使用

      <select id="getAllUser" resultType="user">
          SELECT * FROM t_user;
      </select>
      
    • typeHandlers(类型处理器)

    • objectFactory(对象工厂)

    • plugins(插件)

    • environments(环境配置)

      • environment(环境变量)

        可以配置多个环境,比如测试环境和开发环境;使用id区分,不能重复

        • transactionManager(事务管理器)

          <transactionManager type="JDBC"></transactionManager>
          <!--type:
          JDBC:表示使用JDBC原生事务管理方式,即可以手动的开启关闭事务,手动的提交和回滚。
          MANAGED:被管理的,例如交给Spring管理。-->
          
        • dataSource(数据库连接池的配置)

          - type:
              POOLED:使用数据库连接池
              UNPOOLED:不使用数据库连接池,链接直接重新创建
              JNDI:表示使用上下文当中的数据源(了解下)
          
    • databaseIdProvider(数据库厂商标识)

    • mappers(映射器)


2.2.1数据库配置db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&\
  characterEncoding=utf8&useUnicode=true&useSSL=false
username=root
password=xxxxxxx
2.2.2创建mapper接口

mybatis中的mapper接口相当于以前的Dao,但是区别在于,mapper仅仅是接口,我们不需要提供实现类

public interface BookMapper{
    /**
     *添加信息
     */
    int insertBook();
}
2.2.3创建MyBatis的映射文件

相关概念:ORM(Object Relationship Mapping)对象关系映射。

  • 对象;Java的实体类对象
  • 关系:关系型数据库
  • 映射:二者之间的对应关系
Java概念数据库概念
属性字段/列
对象记录/行

1、映射文件的命名规则:

表所对应的实体类的类名+Mapper.xml

一个映射文件对应一个实体类,对应一张表的操作

MyBatis映射文件用于编写SQL,访问以及操作表中的数据

MyBatis映射文件存放的位置是src/main/resources/mappers目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--映射文件:主要用于配置SQL语句和Java对象之间的映射,使SQL语句查询出来的数据能够被封装成Java对象-->
<!-- mapper为映射的根节点-->
<!-- mapper为映射的根节点,namespace指定Mapper接口的完整类名
     mybatis会依据这个接口动态创建一个实现类去实现这个接口,
     而这个实现类是一个Mapper对象-->
<mapper namespace="Dao.BookMapper">
    <!--id ="接口中的方法名
     parameterType="传入的参数类型"
     resultType = "返回实体类对象,使用包,类名"-->
    <!--查询操作-->
    <select id="findById" parameterType="int"
            resultType="POJO.Book">
            select * from book where id = #{id}
    </select>
    <!--添加操作-->
    <insert id="insertBook">
        insert into book VALUES (NULL, 'admin', '123456', 23, '男', '12345@qq.com');
    </insert>
</mapper>
2.2.4测试功能

从XML中构建SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

@Test
public void testSession() throws IOException{
//创建字符输入流,读取mybatis-config.xml文件内容到reader对象中
	Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
//	InputStream inputStream = Resources.getResourceAsStream(resource);也可以使用字节输入流
//初始化Mybatis数据库,创建SqlSessionFactory类的实例,build()使用Reader字符流封装了XML文件形式的配置信息
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}

从SqlSessionFactory中获取SqlSession

既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。

openSession() 获得 SqlSession 默认是不自动提交事务,因此需要自己手动提交。

//获取sql的会话对象sqlSession,是MyBatis提供的操作数据库的对象
SqlSession sqlSession = sqlSessionFactory.openSession(); 
//设置自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true); 

在实际应用中使用工具类获取SqlSession对象会更加方便,将以上代码封装进工具类中。

//通过工具类获取SqlSession对象
SqlSession session = MybatisUtils.getSession();

通过 SqlSession 实例来直接执行已映射的 SQL 语句

//1.创建SqlSession实例,执行持久化操作
SqlSession sqlSession = sqlSessionFactory.openSession();
//2.1通过sql的唯一标识id
Book book = sqlSession.selectOne("findById",1);//Book是实体类,selectOne()是SqlSession提供的查询方法

通过获取Mapper的代理实现类对象,执行接口方法

//2.2获取BookMapper的代理实现类对象
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
//调用mapper接口中的方法,实现添加信息的功能
int result = bookMapper.insertBook();
//提交事务
sqlSession.commit();
//关闭事务
sqlSeesion.close();

3.MyBatis的增删改查

3.1查询

3.1.1 根据id查询数据

BookDao.java接口

//查询一个实体类对象
public interface BookDao {
    public Book getBookById(int id);
}

BookDao.xml映射文件:

<mapper namespace="Dao.BookDao">
    <!--查询操作-->
    <select id="getBookById" parameterType="int"
            resultType="pojo.Book">
        select * from book where id = #{id}
    </select>
</mapper>

BookTest.java测试方法:

@Test
public void testGetBookById() throws IOException{
    //接收参数,该id以后由前端传递过来
    int id = 1;
    
    //1.使用工具类获取会话工厂SqlSessionFactory
    //2.创建SqlSession实例,执行持久化操作
	SqlSession sqlSession = MybatisUtils.getSession();
	//3.BookDao是一个接口,获取Mapper
	BookDao bookDao=sqlSession.getMapper(BookDao.class);
    //4.执行方法
    Book book = bookDao.getBookById(id);
    //5.释放资源
    sqlSession.close();
}

注意:现在我们感觉测试这部分代码写起来特别麻烦,我们可以先忍忍。以后我们只会写上面的第3步的代码,其他的都不需要我们来完成。

3.1.1.1 参数占位符
  • mybatis提供了两种参数占位符:

    • #{}:是预编译处理。执行SQL时,会将#{}占位符替换成?,将来自动设置参数值。从上述例子可以看出使用#{}底层使用的是PreparedStatement的set方法来赋值。
    • ${}:是字符串替换。就是把${}替换成变量的值底层使用的是Statement,会存在SQL注入问题。
3.1.1.2 parameterType的使用

对于有参数的mapper接口方法,我们在映射配置文件中应该配置parameterType来指定参数类型。只不过该属性都可以省略。

3.1.1.3 SQL语句中特殊字段处理

以后肯定会在SQL语句中写一下特殊字符,比如某一个字段大于某个值,如下图:

在这里插入图片描述

可以看出报错了,因为映射配置文件是xml类型的问题,而 > < 等这些字符在xml中有特殊含义,所以此时我们需要将这些符号进行转义,可以使用以下两种方式进行转义。

  • 转义字符

    <select id="selectById" resultMap="mapp">
        select *
        from book
        where id &lt; #{id}
    </select>
    

    如上面代码所示中的&lt;就是<的转义字符。

  • <![CDATA[内容]]>

    <select id="selectById" resultMap="mapp">
        select *
        from book
        where id
         <![CDATA[<]]> #{id}
    </select>
    
3.1.2使用resultMap查询所有数据
3.1.2.1 字段名和属性名不一致的情况
  • 方式1:起别名的方法

我们可以在编写sql语句的时候给这个字段用as起别名,将别名定义成和属性名一致即可。

这里假设我数据库中的字段名publication_date,在实体类中属性名是publicationDate

 <select id="getBookById" parameterType="int"
            resultType="pojo.Book">
     select 
     id,title,author,publication_date as publicationDate,price
     from book
</select>

而上面的SQL语句中的字段列表书写麻烦,如果表中还有更多的字段,同时其他的功能也需要查询这些字段时就显得我们的代码不够精炼。Mybatis提供了sql 片段可以提高sql的复用性。

  • SQL片段

    • 将需要复用的SQL片段抽取到SQL标签中
    <sql id="book_column">
        id,title,author,publication_date as publicationDate,price
    </sql>
    

    id属性是唯一标识,引用时也是通过该值进行引用。

    • 在原sql语句中进行引用

    使用include标签引用上述的SQL片段,而refid指定上述SQL片段的id值。

     <select id="getBookById" parameterType="int"
                resultType="pojo.Book">
         select 
         <include refid="book_column" />
         from book 
    </select>
    
  • 方式2:使用resultMap

    //编写接口方法
    List<Book> findAll();
    
    <!--编写SQL语句-->
    <mapper namespace="Dao.BookDao">
        <!--
                id:完成主键字段的映射
                    column:表的列名
                    property:实体类的属性名
                result:完成一般字段的映射
                    column:表的列名
                    property:实体类的属性名
            -->
        <resultMap id="BookMap" type="POJO.Book">
            <id property="id" column="id"/>
            <result property="title" column="title"/>
            <result property="author" column="author"/>
            <result property="publication_date" column="publicationDate"/>
            <result property="price" column="price"/>
        </resultMap>
        <!--查询结果集-->
        <select id="findAll" resultMap="BookMap">
            select * from book
        </select>
    </mapper>
    
    //编写测试方法
    @Test
    public void testFindAll(){
        //SqlSession执行映射文件中定义的SQL,并返回映射结果
            List<Book> books = bookDao.findAll();
            for(Book book:books){
                System.out.println(book);
            }
    }
    

    这里建议使用resultMap的方式,起别名的方法虽然也能解决这个问题,但是复用性不高。

    如果还有功能只需要查询部分字段,而不是查询所有字段,那么我们就需要再定义一个 SQL 片段,这就显得不是那么灵活。

  • 方式3:开启下划线→小驼峰的配置,在mybatis-config.cml文件当中添加如下配置

    <settings>
        <!-- 下划线 自动映射 驼峰 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
3.1.2.2 多对一关系

POJO:多个Emp对应一个Dept,多个员工对应一个部门对象

public class Emp {
    private Integer empId;
    private String empName;
    private Integer age;
    private String gender;

    private Dept dept;
    
    // getter/setter/toString
}
  • 方式一:级联方式处理,即一次连接出所有字段
//接口方法
Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);
<resultMap id="getEmpAndDeptByEmpId" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <!-- 级联方式:多对一的映射关系 -->
    <result column="dept_id" property="dept.deptId"></result>
    <result column="dept_name" property="dept.deptName"></result>
</resultMap>

<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpId">
    SELECT *
    FROM t_emp
    LEFT JOIN t_dept
    ON t_emp.dept_id = t_dept.dept_id
    WHERE emp_id = #{empId};
</select>

在这里插入图片描述

  • 方式二:association标签:

    association:处理多对一的映射关系(处理实体类类型的属性)

    property:设置需要处理映射关系的属性的属性名

    javaType:设置处理的属性的类型

<resultMap id="getEmpAndDeptByEmpId" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <!-- association :专门处理多对一、一对一的映射关系 -->
    <association property="dept" javaType="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
    </association>
</resultMap>

<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpId">
    SELECT *
    FROM t_emp
    LEFT JOIN t_dept
    ON t_emp.dept_id = t_dept.dept_id
    WHERE emp_id = #{empId};
</select>

  • 方式三:分布查询,先查询出Emp,再根据Emp中的deptId查询Dept

    • 👍优点:可以延迟加载,但必须在核心配置文件设置全局配置(看核心配置详解)

    • EmpMapper.java

    /**
     * 分步查询的第一步
     * @param empId
     * @return
     */
    Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
    
    • DeptMapper.java
    /**
     * 分步查询的第二步
     * @param deptId
     * @return
     */
    Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
    
    • EmpMapper.xml
    <resultMap id="getEmpAndDeptByStepResultMap" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
    
        <!--
            property: 关联的类型
            fetchType: eager 表示全局配置了懒加载,但是这里我还是想立即加载
            select: 第二步查询的唯一标识
            column: 第一步查询出来的外键
        -->
        <association property="dept" fetchType="eager"
                     select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                     column="dept_id" ></association>
    </resultMap>
    
    <!-- Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId); -->
    <select id="getEmpAndDeptByStepOne" resultMap="getEmpAndDeptByStepResultMap">
        SELECT * FROM t_emp WHERE emp_id = #{empId};
    </select>
    
    • DeptMapper.xml
    <!-- Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);
         这里没有使用 ResultMap, 因为开启了 下划线 转 驼峰的配置
    -->
    <select id="getEmpAndDeptByStepTwo" resultType="dept">
        SELECT * FROM t_dept WHERE dept_id = #{dept_id};
    </select>
    
3.1.2.3 一对多关系

POJO:一个 Dept 对应多个 Emp

public class Dept {
    private Integer deptId;
    private String deptName;

    private List<Emp> emps;
  
    // getter/setter/toString
}

方式一:collection标签

Dept getDeptAndEmpsByDeptId(@Param("deptId") Integer deptId);
<resultMap id="getDeptAndEmpsByDeptIdResultMap" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>

    <!--
        property: 关联的属性.
        ofType: 集合内部元素的类型.
     -->
    <collection property="emps" ofType="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
    </collection>
</resultMap>

<!-- Dept getDeptAndEmpsByDeptId(@Param("deptId") Integer deptId); -->
<select id="getDeptAndEmpsByDeptId" resultMap="getDeptAndEmpsByDeptIdResultMap">
    SELECT *
    FROM t_dept
    LEFT JOIN t_emp
    ON t_dept.dept_id = t_emp.dept_id
    WHERE t_dept.dept_id = #{deptId};
</select>

方式二:分布查询,先查询部门,再根据部门的id查询部门下的员工

  • DeptMapper.java
/**
 * 分步查询 查询部门以及部门中的员工的信息
 * @param deptId
 * @return
 */
Dept getDeptAndEmpdsStepOne(@Param("deptId") Integer deptId);
  • EmpMapper.java
/**
 * 分布查询的第二步:查询出一个部门下的员工
 * @param empId
 * @return
 */
Emp getDeptAndEmpdsStepTwo(@Param("empId") Integer empId);
  • DeptMapper.xml
<resultMap id="getDeptAndEmpdsStepOneResultMap" type="dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    
    <!-- fetchType="eager" 关闭懒加载 -->
    <collection property="emps" fetchType="eager" ofType="emp"
                 select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpdsStepTwo"
                 column="dept_id">

    </collection>
</resultMap>

<!-- Dept getDeptAndEmpdsStepOne(@Param("deptId") Integer deptId); -->
<select id="getDeptAndEmpdsStepOne" resultMap="getDeptAndEmpdsStepOneResultMap">
    SELECT *
    FROM t_dept
    WHERE dept_id = #{deptId};
</select>
  • EmpMapper.xml
<!-- Emp getDeptAndEmpdsStepTwo(@Param("empId") Integer empId);
     这里没有使用resultMap, 因为已经开启了下划线转驼峰的配置
-->
<select id="getDeptAndEmpdsStepTwo" resultType="emp">
    SELECT *
    FROM t_emp
    WHERE emp_id = #{empId};
</select>
3.1.3 多条件查询

BookDao 接口中定义多条件查询的方法。

3.1.3.1 MyBatis获取多参数:

而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现

  • 使用 @Param("参数名称") 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位

    List<Book> selectByCondition(@Param("title") int status, @Param("author") String companyName,@Param("price") String brandName);
    
  • 将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和实体类属性名保持一致。

    List<Book> selectByCondition(Book book);
    
  • 将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和map集合中键的名称一致。

    List<Book> selectByCondition(Map map);
    
3.1.3.2 查询到Map集合

查询的结果没有对应的实体类的时候,就可以使用Map集合。

resultType设置成map即可

❗❗查询为null的字段是不会放到Map集合里面的。

存放一条记录

Map<String, Object> getUserById(@Param("id") String id);
<select id="getUserById" resultType="map">
    SELECT * FROM t_user WHERE id = ${id}
</select>

存放多条记录

  • 方式一:使用@MapKey()注解

    @MapKey("id")
    Map<String, Object> getAllUserToMap();
    
    <select id="getAllUserToMap" resultType="map">
        SELECT * FROM t_user;
    </select>
    
    // Map<id, User>运行结果
    {
      3={password=123, gender=, id=3, age=23, email=12345@qq.com, username=root}, 
      4={password=123456, gender=, id=4, age=23, email=12345@qq.com, username=admin}, 
      5={password=123456, gender=, id=5, age=23, email=12345@qq.com, username=admin}, 
      6={password=123456, gender=, id=6, age=20, email=geek_zh@163.com, username=zs}
    }
    
  • 方式二:使用List<Map<>>,泛型为Map的List

    List<Map<String, Object>> getAllUser();
    
    <select id="getUserById" resultType="map">
        SELECT * FROM t_user;
    </select>
    
3.1.3.3 模糊查询

BookDao.xml映射配置文件中编写statement,使用resultMap而不是resultType

<select id="selectByCondition" resultMap="bookRetMap">
    select *
    from book
    where title = #{title}
    and author like #{author}
    and price &lt; #{price}
</select>

模糊查询书的作者的名字,例如查询作者姓王的、书名为Java程序设计的、价格小于80的所有书籍信息。

测试方法

//定义测试方法
@Test
public void testSelectByCondition() throws IOException{
    //接收参数
    String title = "Java程序设计";
    String author = "王";
    int price = 80;
    
    //处理参数
    author = "%"+author+"%";
    
    //1.通过工具类获取SqlSession
    SqlSession session = MyBatisUtils.getSession();
    
    //2.获取Mapper接口的代理对象
    BookDao bookdao = seesion.getMapper(Book.class);
    
    //3.执行方法
    //方式一:接口方法参数使用@Param方式的方法
    //List<Book> books = bookdao.selectByCondition(title,author,price);
    //方式二:接口方法参数使用实体类对象方式调用 
    //封装对象
    /*Book book = new Book();
    book.setTitle(title);
    book.setAuthor(author);
    book.setPrice(price);*/
    //执行方法
  //List<Book> books = bookdao.selectByCondition(book);
    //方式三:接口方法参数是map集合对象 方式调用的方法
    Map map = new HashMap();
    map.put("title",title);
    map.put("author",author);
    map.put("price",price);
    List<Book> books = bookdao.selectByCondition(map);
    System.out.println(books);
    
    //4.释放资源
    session.close();
}
3.1.3.4 动态SQL-if、where、trim

上述例子的功能实现存在很大的问题。它会强行要求用户查询时必须输入三个条件才允许查询,但现实有可能出现用户只想输入一个条件的情况

例如,用户只想查询书名为Java程序设计的所有书籍亦或者想要查询作者姓王的价格小于80的书籍……

这样子的SQL语句就会需要发生改变,再用上面例子的SQL语句就会无法查询出来。

针对上述的需要,MyBatis对动态SQL有很大的支撑:

  • if
  • choose(when,otherwise)
  • trim(where,set)
  • foreach

我们先来看if标签和where标签

  • if标签:条件判断

    • test属性:逻辑表达式

      <select id="selectByCondition" resultMap="brandResultMap">
          select *
          from book
          where
              <if test="title != null">
                  and title = #{title}
              </if>
              <if test="author != null and author != '' ">
                  and author like #{author}
              </if>
              <if test="price != null and price != '' ">
                  and price &lt; #{price}
              </if>
      </select>
      

      如上这种SQL语句就会根据传递的参数值进行动态的拼接。当传递的参数为null或者为空字符就不执行那条语句。

      但是这样也会有问题,若此时给的参数值是:

      Map map = new HashMap();
          //map.put("title",title);
          map.put("author",author);
          map.put("price",price);
      

      拼接的SQL语句就变成了:

      select * from book where and author like ? and price < ?
      

      where关键字后直接跟了and关键字,这就是一条错误的SQL语句。

      这个就需要用到where标签

  • where标签

    • 作用:

      • 替换where关键字
      • 会动态的去掉第一个条件前的and
      • 如果所有的参数没有值则不加where关键字
      <select id="selectByCondition" resultMap="brandResultMap">
          select *
          from book
          <where>
              <if test="title != null">
                  and title = #{title}
              </if>
              <if test="author != null and author != '' ">
                  and author like #{author}
              </if>
              <if test="price != null and price != '' ">
                  and price &lt; #{price}
              </if>
          </where>
      </select>
      

      注意:需要给每个条件前都加上and关键字

  • trim标签

    Mybatis的trim标签一般用于去除sql语句中多余的and关键字、逗号,或者给sql语句前拼接where、set、values等前缀或者后缀,可用于选择性插入、更新、删除和条件查询等操作。

    ✈prefix:给sql语句拼接的前缀

    ✈suffix:给sql语句拼接的后缀

    ✈prefixOverrides:去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"–>等同于where标签

    ✈suffixOverrides:去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定

    <select id="selectByCondition" resultMap="Map">
        select *
        from book
        <trim prefix="where" prefixOverrides="and">
            <if test="title != null">
                and title = #{title}
            </if>
            <if test="author != null and author != '' ">
                and author like #{author}
            </if>
            <if test="price != null and price != '' ">
                and price &lt; #{price}
            </if>
        </trim>
    </select>
    
3.1.4 单个条件

在这里插入图片描述

3.1.4.1 动态SQL-choose(when,otherwise)

看一下这个例子,当出现这种情况的时候,就需要使用到choose(when,otherwise)标签来实现,choose标签相当于Java中的switch语句。

<select id="selectByConditionSingle" resultMap="brandResultMap">
    select *
    from tb_brand
    <where>
        <choose><!--相当于switch-->
            <when test="status != null"><!--相当于case-->
                status = #{status}
            </when>
            <when test="companyName != null and companyName != '' "><!--相当于case-->
                company_name like #{companyName}
            </when>
            <when test="brandName != null and brandName != ''"><!--相当于case-->
                brand_name like #{brandName}
            </when>
        </choose>
    </where>
</select>

3.2新增

3.2.1 添加数据
//接口方法
public void insertBook(Book book);
  <!--插入操作-->
   <insert id="insertBook" parameterType="POJO.Book">
    insert into book(id,title,author,publication_date,price) 
       values (#{id},#{title},#{author},#{publication_date},#{price})
   </insert>
//测试方法
@Test
public void testInsertBook(){
    Book book = new Book();
    book.setId("5");
    book.setTitle("Python'");
    book.setAuthor("张三");
    book.setPublication_date("2011-10-20 16:41:21");
    BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
    bookMapper.insertBook(book);
    sqlSession.commit();
    sqlSession.close();
}
3.2.1.1 获取自增主键
<insert id="add" useGeneratedKeys="true" keyProperty="id">
    insert into book(title,author,publication_date,prie) values (#{title},#{author},#{publication_date},#{price})
</insert>

在insert标签中添加如下属性:

  • userGeneratedKeys:能够获取自动增长的主键值。true表示获取
  • keyProperty:指定将获取到的主键值封装到哪个属性里

3.3修改

3.3.1 修改数据
public void updateBook(Book book);
public void upDate(Book book);
    <!--方式1:更新操作(改变所有数据)-->
    <update id="updateBook" parameterType="pojo.Book">
        update book set title=#{title},author=#{author},publication_date=#{publication_date},price=#{price} where id=#{id}
    </update>
<!--方式2:使用动态SQL改变部分数据-->
<update id="upDate" parameterType="pojo.Book">
    update book
    <set>
        <if test="title != null and title != ''">
            title = #{title}
        </if>
        <if test="author != null and author != ''">
            author = #{author}
        </if>
        <if test="publication_date != null and publication_date != ''">
            publication_date = #{publication_date}
        </if>
        <if test="price != null and price != ''">
            price = #{price}
        </if>
    </set>
    where id = #{id}
</update>

set标签可以用于动态包含需要更新的列,忽略其他不需要更新的列

@Before
public void init(){
    String resources = "mybatis-config.xml";
    try{
        Reader reader = Resources.getResourcesAsReader(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession = sqlSessionFactory.openSession();
    }catch(IOException e){
        e.printStackTrace();
    }
}
@Test
public void testUpdate() throws IOException{
    //获取mapper
    BookDao bookDao = sqlSession.getMapper(BookDao.class);
    //方式1:适用于更改全部数据
    Book book1 = bookDao.getBookById(5);
    book1.setTitle("java");
    //···
    bookDao.updateBook(book1);
    //方式2:可单独修改部分数据
    Book book2 = new Book();
    book2.setTitle("python");
    book2.setId(5);
    bookDao.update(book2);
    
    sqlSession.commit();
    sqlSession.close();
}

3.4删除

3.4.1 删除一行数据
 public void deleteBook(int BookId);
  <!--删除操作-->
    <delete id="deleteBook" parameterType="pojo.Book">
        delete from book where id=#{id}
    </delete>
 @Test
public void testDeleteBook() throws IOException {
    //接收参数
    int id = 6;
    //3. 获取Mapper接口的代理对象
    BookDao bookMapper = sqlSession.getMapper(BookDao.class);
    //4. 执行方法
    bookMapper.deleteBook(id);
    //提交事务
    sqlSession.commit();
    //5. 释放资源
    sqlSession.close();
}
3.4.2批量删除处理

BrandMapper 接口中定义删除多行数据的方法。

/**
  * 批量删除
  */
void deleteByIds(int[] ids);

参数是一个数组,数组中存储的是多条数据的id

如果不使用foreach标签的话

delete from tb_brand where id in(${ids})
--只能使用${},#{}会默认给数据加上单引号,而在in中如果存在单引号的话,它只会删除第一条数据
3.4.2.1 动态SQL-foreach

用来迭代任何可迭代的对象(如数组,集合)。

  • collection 属性:
    • mybatis会将数组参数,封装为一个Map集合。
      • 默认:array = 数组
      • 使用@Param注解改变map集合的默认key的名称
  • item 属性:本次迭代获取到的元素。
  • separator 属性:集合项迭代之间的分隔符。foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。
  • open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
  • close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
<delete id="deleteByIds">
    delete from tb_brand where id
    in
    <foreach collection="array" item="id" seperatoe="," open="(" close=")">
        #{id}
    </foreach>
</delete>

加入数组中的id数据是{1,2,3},那么拼接后的sql语句就是:

delete from tb_brand where id in(1,2,3);

4.MyBatis的缓存

4.1 MyBatis的一级缓存

  • MyBatis的一级缓存是SqlSession级别的,即通过同一个sqlSession查询的数据会被缓存,再次使用同一个sqlSession查询同一条数据,会从缓存中获取。
  • 在MyBatis中一级缓存是默认开启的。

在这里插入图片描述

  • 使一级缓存失效的四种情况:
  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession再次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession再次查询期间手动清空了缓存sqlSession.clearCache()

4.2 MyBatis的二级缓存

MyBatis的二级缓存是SqlSessionFactory级别的,即通过一个SqlSessionFactory所获取的sqlSession对象查询的数据会被缓存,在通过同一个SqlSessionFactory所获取的SqlSession查询相同的数据会从缓存中获取

二级缓存的开启条件

  1. 在核心配置文件中,设置全局属性配置 cacheEnabled=“true”, 默认为 true,不需要设置

    <settings>
      <setting cacheEnabled="true"></setting>
    </settings>
    
  2. 核心配置文件,设置全局属性配置

    <mapper namespace="com.atguigu.mybatis.mapper.DynamicMapperSQLMapper">
        <cache/>
        <!-- 余下代码 -->
    </mapper>
    
  3. ❗ ❗二级缓存必须是在 SQLSession 关闭或提交之后有效。(即SQLSession关闭或提交后,一级缓存当中的数据才会保存到一级缓存中)

  4. ❗❗二级缓存必须是在 SQLSession 关闭或提交

使二级缓存失效的情况

❗❗两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

4.3 二级缓存相关配置(简单了解即可)

在这里插入图片描述

4.4 MyBatis缓存查询的顺序

  1. 先查询二级缓存,因为二级缓存当中可能会有其他线程已经查询出来的数据。
  2. 二级缓存没有命中,则再查询一级缓存。
  3. 一级缓存也没有命中,则执行查询数据库。
  4. SQLSession关闭之后,一级缓存当中的数据会写入到二级缓存。

5.逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate 是支持正向工程的。 逆向工程:先创建数据库表,由框架负责根据数据库表反向生成如下资源:

  1. Java实体类。
  2. Mapper接口。
  3. Mapper映射文件

用到的时候再看吧,工作中也没啥用。


6.分页插件

6.1分页插件的使用步骤

①添加依赖

<!-- 分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

②配置分页插件

在MyBatis的核心配置文件中配置插件

<configuration>
    <plugins>
        <!-- 设置分页插件 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
</configuration>

6.2分页插件的使用

@Test
public void testPage(){
    SqlSession sqlSession = MyBatisUtils.getSession();
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    //查询功能之前开启分页
    PageHelper.startPage(1,4);
    List<Emp> list = mapper.slectByExample(null);//没有数据就是查询所有数据
    //查询功能之后可以获取分页相关的所有数据
    PageInfo<Emp> pageInfo = new PageInfo<>(list,5);
}

7.注解方式编写CRUD

除了在xml文件中编写SQL语句之外,还可以使用注解的方法在接口中直接编写,例如BookTest.java

public interface BookTest{
    //映射查询语句
    @Select("select * from book where id = #{id}")
    Book selectBook(int id);
    //映射插入语句
    @Insert("insert into book(id,title,author,publication_date,price)"
            +"value (#{id},#{title},#{author},#{publication_date},#{price})")
    int insertBook(Book book);
    //映射更新语句
    @Update("update book set title=#{title},author=#{author},publication_date=#{publication_date},price=#{price} "
            +"where id=#{id}")
    int updateBook(Book book);
    //映射删除语句
    @Delete(" delete from book where id=#{id}")
    int deleteBook(int id);
}

注意:

  • 注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的 statement

Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:

  • 查询 :@Select
  • 添加 :@Insert
  • 修改 :@Update
  • 删除 :@Delete

所以,注解完成简单功能,配置文件完成复杂功能。


logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>

    <logger name="com.itheima" level="DEBUG" additivity="false">
        <appender-ref ref="Console"/>
    </logger>


    <!--

      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
     , 默认debug
      <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
      -->
    <root level="DEBUG">
        <appender-ref ref="Console"/>
    </root>
</configuration>

MyBatisUtils.java

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory=null;
    //初始化SqlsessionFactory对象
    static {
        try {
            //使用MyBatis提供的Resource类加载MyBatis的配置文件
            Reader reader= Resources.getResourceAsReader("mybatis-config.xml");
            //构建SqlSessionFactory
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //获取SqlSession对象的静态方法
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }
}

=注解完成简单功能,配置文件完成复杂功能。==


logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>

    <logger name="com.itheima" level="DEBUG" additivity="false">
        <appender-ref ref="Console"/>
    </logger>


    <!--

      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
     , 默认debug
      <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
      -->
    <root level="DEBUG">
        <appender-ref ref="Console"/>
    </root>
</configuration>

MyBatisUtils.java

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory=null;
    //初始化SqlsessionFactory对象
    static {
        try {
            //使用MyBatis提供的Resource类加载MyBatis的配置文件
            Reader reader= Resources.getResourceAsReader("mybatis-config.xml");
            //构建SqlSessionFactory
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //获取SqlSession对象的静态方法
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession();
    }
}

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值