项目管理与SSM框架 MyBatis

目录

一、MyBatis介绍

1.1 什么是框架

1.2什么是ORM框架​编辑

1.3什么是MyBatis

二、入门案例

2.1 环境搭建

2.2 创建持久层接口和映射文件

2.3 测试持久层接口方法

2.4 MyBatis核心对象及工作流程

MyBatis核心对象

MyBatis工作流程

2.5 使用SqlSession操作数据库(了解)

2.6Mapper动态代理原理

查看代理方式

三、MyBatis增删改查

3.1 新增

3.2 修改

3.3 根据id删除

3.4 模糊查询

3.5 分页查询

顺序传参

@Param传参

 POJO传参

Map传参

3.6 聚合查询和主键回填

查询用户总数

主键回填

四、MyBatis配置文件

4.1 

4.2 

4.3 

为一个类配置别名

为一个所有包下的所有类配置别名

4.4 

4.5 

事务管理

连接池

4.6 

五、MyBatis映射文件

5.1

5.2 、

5.3 特殊字符处理

六、MyBatis动态Sql

6.1 

 6.2 、

6.3 、、

6.4 foreach遍历数组

6.5 foreach遍历Collection

 6.6 foreach遍历Map

七、MyBatis缓存

7.1 缓存介绍

7.2 一级缓存

7.3 清除一级缓存

7.4 MyBatis二级缓存

开启二级缓存

八、MyBatis关联查询

 8.1 一对一关联查询

8.2 一对多关联查询

 8.3多对多关系映射

 九、MyBatis分解式查询

9.1 一对多分解式查询 

 9.2 一对一分解式查询

 9.3 延迟加载

开启延迟加载

测试延迟加载

十、MyBatis注解开发

10.1 基于注解实现增删改查 

10.2 动态sql

使用脚本标签(了解即可千万别用,又臭又长)

在方法中构建动态Sql

 10.3 自定义映射

10.4 基于注解开启二级缓存

10.5 基于注解实现一对一关联查询

10.6 基于注解实现一对多关联查询 

10.7 注解和映射文件对比

十一、PageHelper分页插件

 十二、MyBatis Generator

12.1 增删改方法

12.2 查询方法

12.3  复杂查询


一、MyBatis介绍

1.1 什么是框架

框架即一个半成品软件。开发者从头开发一个软件需要花费大量精力,于是有一些项目组开发出半成品软件,开发者在这些软件的基础上进行开发,这样的软件就称之为框架。

如果将开发完成的软件比作是一套已经装修完毕的新房,框架就好比是一套已经修建好的毛坯房。用户直接购买毛坯房,保证建筑质量和户型合理的同时可以进行风格的自由装修。

使用框架开发的好处:

  1. 省去大量的代码编写、减少开发时间、降低开发难度。
  2. 限制程序员必须使用框架规范开发,增强代码的规范性,降低程序员之间沟通及日后维护的成本。
  3. 将程序员的注意力从技术中抽离出来,更集中在业务层面。

使用框架就好比和世界上最优秀的软件工程师共同完成一个项目,并且他们完成的还是基础、全局的工作。 

1.2什么是ORM框架

 ORM(Object Relationl Mapping),对象关系映射,即在数据库和对象之间作映射处理。

1.3什么是MyBatis

 MyBatis是一个半自动的ORM框架,其本质是对JDBC的封装。使用MyBatis不需要写JDBC代码,但需要程序员编写SQL语句。之前是apache的一个开源项目iBatis,2010年改名为MyBatis。

补充:

Hibernate也是一款持久层ORM框架,多年前的市场占有率很高,但近年来市场占有率越来越低。

MyBatis与Hibernate的比较:

  • MyBatis是一个半自动的ORM框架,需要手写SQL语句。
  • Hibernate是一个全自动的ORM框架,不需要手写SQL语句。
  • 使用MyBatis的开发量要大于Hibernate。

为什么Hibernate市场占有率越来越低:

  • 对于新手学习Hibernate时间成本比MyBatis大很多,MyBatis上手很快。
  • Hibernate不需要写SQL语句是因为框架来生成SQL语句。对于复杂查询,开发者很难控制生成的SQL语句,这就导致SQL调优很难进行。
  • 之前的项目功能简单,数据量小,所以使用Hibernate可以快速完成开发。而近年来项目的数据量越来越大,而互联网项目对查询速度要求也很高,这就要求我们一定要精细化的调整SQL语句。此时灵活性更强,手动编写SQL语句的MyBatis慢慢代替了Hibernate使用。
  • 在高并发、大数据、高性能、高响应的互联网项目中,MyBatis是首选的持久框架。而对于对性能要求不高的比如内部管理系统等可以使用Hibernate。

二、入门案例

2.1 环境搭建

1、创建maven工程,引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zj</groupId>
    <artifactId>learnMyBatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--MyBatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!--MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--log4j日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>
</project>

2、创建mybatis核心配置文件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="mysql">
        <environment id="mysql">
            <!--事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:///mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

 3、将log4j.properties文件放入resources中,让控制台打印SQL语句。

# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
log4j.rootCategory=debug, CONSOLE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
#log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%d{MM/dd HH:mm:ss}] %-6r [%15.15t] %-5p %30.30c %x - %m\n

4、创建实体类

package com.zj;

public class User {
    private int id;
    private String username;
    private String sex;
    private String address;
//构造、get\set略
}

2.2 创建持久层接口和映射文件

1、在java目录创建持久层接口

package com.zj.mapper;

import com.zj.pojo.User;
import java.util.List;

/*主要操作User类和数据库之间的交互*/
public interface UserMapper {
    List<User> selectAllUser();
}

2、在resource目录创建映射文件 

<?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="com.zj.mapper.UserMapper">
    <!--id="selectAllUser";id属性值为接口的方法名称
    resultType="com.zj.pojo.User";定义返回值类型
    -->
       <select id="selectAllUser" resultType="com.zj.pojo.User">
            SELECT * FROM user;
       </select>
</mapper>

 3、将映射文件配置到mybatis核心配置文件中

    <!--注册映射文件(项目加载的时候先加载的是核心配置文件)-->
    <mappers>
        <mapper resource="com.zj.mapper.UserMapper.xml"/>
    </mappers>

映射文件注意事项:

  • 映射文件要和接口名称相同。

  • 映射文件要和接口的目录结构相同,而且映射文件所在的文件结构要一层层的建。

  • 映射文件中namespace属性要写接口的全名。

  • 映射文件中标签的id属性是接口方法的方法名。

  • 映射文件中标签的resultType属性是接口方法的返回值类型。

  • 映射文件中标签的parameterType属性是接口方法的参数类型。

  • 映射文件中resultType、parameterType属性要写全类名,如果是集合类型,则写其泛型的全类名。

2.3 测试持久层接口方法


import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

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

public class TestUserMapper {
    @Test
    public void TestSelectAllUser() throws IOException {
        /*1、读取核心配置文件*/
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        /*2、SqlSessionFactoryBuilder对象获取SqlSessionFactory对象(建造者用映射文件材料建设了工厂)*/
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);

        /*3、SqlSessionFactory对象获取SqlSession(工厂建造了了SqlSession)*/
        SqlSession sqlSession = sessionFactory.openSession();

        /*4、SqlSession 对象获取接口的代理对象*/
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        /*5、代理对象执行方法*/
        List<User> users = mapper.selectAllUser();

        for (User user : users) {
            System.out.println(user);
        }

        /*6、释放资源*/
        sqlSession.close();
        resourceAsStream.close();

    }
}

2.4 MyBatis核心对象及工作流程

MyBatis核心对象

  • SqlSessionFactoryBuilder

    SqlSession工厂构建者对象,使用构造者模式创建SqlSession工厂对象。

  • SqlSessionFactory

    SqlSession工厂,使用工厂模式创建SqlSession对象。

  • SqlSession

    该对象可以操作数据库,也可以使用动态代理模式创建持久层接口的代理对象操作数据库。

  • Mapper

    持久层接口的代理对象,他具体实现了持久层接口,用来操作数据库。

MyBatis工作流程

  1. 创建SqlSessionFactoryBuilder对象

  2. SqlSessionFactoryBuilder对象构建了SqlSessionFactory对象:构造者模式

  3. SqlSessionFactory对象生产了SqlSession对象:工厂模式

  4. SqlSession对象创建了持久层接口的代理对象:动态代理模式

  5. 代理对象操作数据库

2.5 使用SqlSession操作数据库(了解)

 除了代理对象能够操作数据库,SqlSession也能操作数据库。只是这种方式在开发中使用的较少,接下来我们使用SqlSession操作数据库:

@Test
public void testFindAll2() throws Exception {
  // (1)读取核心配置文件
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  // (2)创建SqlSessionFactoryBuilder对象
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  // (3)SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
  SqlSessionFactory factory = builder.build(is);
  // (4)SqlSessionFactory对象获取SqlSession对象
  SqlSession session = factory.openSession();
  // (5)SqlSession直接操作数据库
  List<User> users = session.selectList("com.zj.mapper.UserMapper.selectAllUser");
  users.forEach(System.out::println);
  // (6)关闭资源
  session.close();
  is.close();
}

2.6Mapper动态代理原理

 通过源码了解MyBatis动态代理的实现。

点开测试类的getMapper方法,发现SqlSession实际上是抽象类,找到该抽象类的默认的实现类DefaultSqlSession

查看代理方式

点开MapperProxy类,查看invoke方法,查看代理对象是如何工作的。

结论:

  • SqlSession的getMapper方法,最终是调用的是JDK动态代理方法,生成一个代理对象,类型就是传入的接口类型。
  • MapperProxy对象通过调用MapperMethod的execute方法定义了代理方式,该方法的底层调用的是SqlSession的方法。

三、MyBatis增删改查

3.1 新增

1、持久层接口添加方法

 void addUser(User user);

2、映射文件添加标签

    <insert id="addUser" parameterType="com.zj.pojo.User">
        INSERT INTO user VALUES (default ,#{username},#{sex},#{address})
    </insert>

3、编写测试方法

    @Test
    public void TestAddUser() throws IOException {
        /*建造者*/
        SqlSessionFactoryBuilder SQLSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        /*建造者根据配置文件建造工厂*/
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory factory = SQLSessionFactoryBuilder.build(resourceAsStream);
        /*工厂生产SqlSession*/
        SqlSession sqlSession = factory.openSession();
        /*SqlSession获取接口的代理对象*/
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        /*代理对象调用方法*/
        mapper.addUser(new User(0,"ddd","男","上海"));
        /*提交事务*/
        sqlSession.commit();

        /*释放资源*/
        sqlSession.close();
        resourceAsStream.close();
    }

注意:

  1. 当接口方法的参数类型为POJO类型时,SQL语句中绑定参数时使用#{POJO的属性名}即可。
  2. MyBatis事务默认手动提交,所以在执行完增删改方法后,需要手动调用SqlSession对象的事务提交方法,否则数据库将不发生改变。

3.2 修改

优化测试类


import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class TestUserMapper {

    InputStream resourceAsStream = null;
    SqlSession sqlSession = null;
    UserMapper mapper =null;


    /*优化*/
    @Before
    public void getSqlSession() throws IOException {
         /*配置文件*/
        resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
          /*建造者*/
        SqlSessionFactoryBuilder SQLSessionFactoryBuilder = new SqlSessionFactoryBuilder();
         /*建造者根据配置文件创建工厂*/
        SqlSessionFactory factory = SQLSessionFactoryBuilder.build(resourceAsStream);
        /*工厂生产SqlSession*/
        sqlSession = factory.openSession();
        /*SqlSession获取代理对象*/
        mapper = sqlSession.getMapper(UserMapper.class);
    }


    @Test
    public void TestSelectAllUser() throws IOException {
        List<User> users = mapper.selectAllUser();
        for (User user : users) {
            System.out.println(user);
        }
    }

    @Test
    public void TestAddUser() throws IOException {
        /*代理对象调用方法*/
        mapper.addUser(new User(0,"ddd","男","上海"));
        /*提交事务*/
        sqlSession.commit();
    }

    @After
    public void close() throws IOException {
      sqlSession.close();
      resourceAsStream.close();
    }
}

1、持久层接口添加方法

void updateUser(User user);

2、映射文件添加标签

    <update id="updateUser" parameterType="com.zj.pojo.User">
        UPDATE user SET username = #{username}, sex = #{sex},address = #{address}  WHERE id = #{id}
    </update>

3、编写测试方法

    @Test
    public void TestUpdateUser(){
        mapper.updateUser(new User(7,"哈哈哈","女","青岛市"));
        /*提交事务*/
        sqlSession.commit();
    }

3.3 根据id删除

1、持久层接口添加方法

    /*根据id删除*/
    void removeUser(int id);

2、映射文件添加标签

    <delete id="removeUser" parameterType="int">
        DELETE FROM user WHERE id = #{id}
    </delete>

注:当方法的参数类型是简单数据类型时,#{}中可以写任意名称

  • 简单数据类型:基本数据类型、字符串等

3、编写测试方法

    @Test
    public void TestRemoveUser(){
        mapper.removeUser(7);
        /*提交事务*/
        sqlSession.commit();
    }

3.4 模糊查询

 1、持久层接口添加方法

    /*模糊查询*/
    List<User> getUserLikeName(String userName);

2、映射文件添加标签

    <select id="getUserLikeName" parameterType="string" resultType="com.zj.pojo.User">
        SELECT * FROM user WHERE username like #{username}
    </select>

模糊查询如果不想在调用方法时参数加%,可以使用拼接参数的方式设置Sql:

    <select id="getUserLikeName" parameterType="string" resultType="com.zj.pojo.User">
        SELECT * FROM user WHERE username like '%${value}%'
    </select>

如果使用#还不想在调用方法的参数中添加%,可以使用<bind><bind>允许我们在 Sql语句以外创建一个变量,并可以将其绑定到当前的Sql语句中。既能防止sql注入还能在传递参数的时候不需要传递百分号。用法如下:

    <select id="getUserLikeName" parameterType="string" resultType="com.zj.pojo.User">
        <bind name="likeName" value="'%'+username+'%'"/>
        SELECT * FROM user WHERE username like #{likeName}
    </select>

#和$的区别:

  1. #表示sql模板的占位符,$表示将字符串拼接到sql模板中。
  2. #可以防止sql注入,一般能用#就不用$。
  3. ${}内部的参数名必须写value。

我们看到在映射文件中,parameterType的值为string而没有写java.lang.String,这是为什么呢?

  • 参数/返回值类型为基本数据类型/包装类/String等类型时,我们可以写全类名,也可以写别名。

3、编写测试方法

    /*模糊查询*/
    @Test
    public void TestGetUserLikeName(){
        List<User> users = mapper.getUserLikeName("%三%");
        for (User user : users) {
            System.out.println(user);
        }

 如果使用sql拼接、或者是<bind>的话直接写参数就行了:

    /*模糊查询*/
    @Test
    public void TestGetUserLikeName(){
        List<User> users = mapper.getUserLikeName("三");
        for (User user : users) {
            System.out.println(user);
        }
    }

3.5 分页查询

分页查询时,Sql语句使用limit关键字,需要传入开始索引和每页条数两个参数。MyBatis的多参数处理有以下方式: 

顺序传参

Sql中的参数使用arg0,arg1...或param1,param2...表示参数的顺序。此方法可读性较低,在开发中不建议使用。

@Param传参

在接口方法的参数列表中通过@Param定义参数名称,在Sql语句中通过注解中所定义的参数名称指定参数位置。此方式参数比较直观的,推荐使用。

1、持久层接口方法

    /*分页查询*/
    List<User> getUserByPage(@Param("start") int start, @Param("size") int size);

2、映射文件

    <!--分页查询-->
    <select id="getUserByPage" resultType="com.zj.pojo.User">
        SELECT * FROM user LIMIT #{start},#{size}
    </select>

3、测试类

    /*分页查询*/
    @Test
    public void TestGetUserByPage(){
        List<User> users = mapper.getUserByPage(0, 3);
        for (User user : users) {
            System.out.println(user);
        }
    }

 POJO传参

自定义POJO类,该类的属性就是要传递的参数,在SQL语句中绑定参数时使用POJO的属性名作为参数名即可。此方式推荐使用。

1、自定义POJO

public class PageQuery {
  private int startIndex;
  private int pageSize;
    // 省略getter/setter/构造方法
}

2、持久层接口方法

    /*分页查询2*/
    List<User> getUserByPage2(PageQuery pageQuery);

3、映射文件

    <!--分页查询2-->
    <select id="getUserByPage2" resultType="com.zj.pojo.User">
        SELECT * FROM user LIMIT #{startIndex},#{pageSize}
    </select>

4、测试类

    /*分页查询2*/
    @Test
    public void TestGetUserByPage2(){
        List<User> userByPage2 = mapper.getUserByPage2(new PageQuery(0, 3));
        for (User user : userByPage2) {
            System.out.println(user);
        }
    }

Map传参

如果不想自定义POJO,可以使用Map作为传递参数的载体,在SQL语句中绑定参数时使用Map的Key作为参数名即可。此方法推荐使用。

1、持久层接口方法

    /*分页查询3*/
    List<User> getUserByPage3(Map<String,Object> params);

2、映射文件

    <!--分页查询3-->
    <select id="getUserByPage3" resultType="com.zj.pojo.User" parameterType="map">
        SELECT * FROM user LIMIT #{start},#{size}
    </select>

3、测试类

    /*分页查询3*/
    @Test
    public void TestGetUserByPage3(){
        Map<String, Object> map = new HashMap<>();
        map.put("start",0);
        map.put("size",3);
        List<User> userByPage3 = mapper.getUserByPage3(map);
        for (User user : userByPage3) {
            System.out.println(user);
        }
    }

3.6 聚合查询和主键回填

查询用户总数

1、持久层方法

int findCount();

2、映射文件

    <!--查询总数-->
    <select id="findCount" resultType="int">
            select count(id) from user;
    </select>

3、测试

    /*查询总数*/
    @Test
    public void TestGFindCount(){
        int count = mapper.findCount();
        System.out.println(count);
    }

主键回填

有时我们需要获取新插入数据的主键值。如果数据库中主键是自增的,这时我们就需要使用MyBatis的主键回填功能。

1、持久层方法

    /*主键回填*/
    void addUser2 (User user);

2、映射文件

    <!--主键回填-->
    <insert id="addUser2" parameterType="com.zj.pojo.User" >
         /*keyProperty:主键属性名称
           keyColumn:主键列名
           resultType:主键类型
           order:执行时机,插入之后执行*/
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
             SELECT LAST_INSERT_ID();
        </selectKey>
           INSERT INTO user VALUES (default ,#{username},#{sex},#{address})
    </insert>

SELECT LAST_INSERT_ID():查询刚刚插入的记录的主键值,只适用于自增主键,且必须和insert语句一起执行。

3、测试

    /*主键回填*/
    @Test
    public void TestAddUser2(){
        User user = new User(0,"啊张","男","海口市");
        mapper.addUser2(user);
        /*提交*/
        sqlSession.commit();
        /*获取回填的id*/
        int id = user.getId();
        System.out.println(id);//9
    }

四、MyBatis配置文件

MyBatis配置文件结构:

-configuration
    -properties(属性)
        -property
    -settings(全局配置参数)
        -setting
    -plugins(插件)
        -plugin
    -typeAliases(别名)
        -typeAliase
        -package
    -environments(环境)
        -environment
            -transactionManager(事务管理)
            -dataSource(数据源)
    -mappers(映射器)
        -mapper
        -package

4.1 <properties>

属性值定义。properties标签中可以定义属性值,也可以引入外部配置文件。无论是内部定义还是外部引入,都可以使用${name}获取值。

例如:我们可以将数据源配置写到外部的db.properties中,再使用properties标签引入外部配置文件,这样可以做到动态配置数据源。

1、编写db.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=123456

2、在配置文件中引入db.properties

    <!--数据库文件-->
    <properties resource="db.properties"></properties>
    <!--配置数据源-->
    <environments default="mysql">
        <environment id="mysql">
            <!--事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <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>

4.2 <settings>

<settings>是配置MyBatis运行时的一些行为的,例如缓存、延迟加载、命名规则等一系列控制性参数。后期我们会使用该标签配置缓存和延迟加载等。

在配置文件中开启二级缓存:

<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>

开启N+1查询的延迟加载:

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

4.3 <typeAliases>

MyBatis对常用类有默认别名支持,比如java.lang.Stirng的别名为string。除此之外,我们也可以使用<typeAliases>设置自定义别名。

为一个类配置别名

<typeAliases>
    <typeAlias type="全类名" alias="别名"></typeAlias>
</typeAliases>

1、配置文件

    <!--配置别名-->
    <typeAliases>
        <typeAlias type="com.zj.pojo.User" alias="User"></typeAlias>
    </typeAliases>

2、映射文件

    <!--查询全部-->
       <select id="selectAllUser" resultType="User">
            SELECT * FROM user;
       </select>

为一个所有包下的所有类配置别名

<typeAliases>
  <package name="包名"></package>
</typeAliases>

此时该包下的所有类都有了别名,别名省略包名,和类名相同。

1、配置文件

    <!--配置别名-->
    <typeAliases>
        <package name="com.zj.pojo"/>
    </typeAliases>

2、映射文件

    <!--分页查询2-->
    <select id="getUserByPage2" resultType="User" parameterType="PageQuery">
        SELECT * FROM user LIMIT #{startIndex},#{pageSize}
    </select>

4.4 <plugins>

<plugins>是配置MyBatis插件的。插件可以增强MyBatis功能,比如进行sql增强,打印日志,异常处理等。使用该标签配置分页插件。

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 设置数据库类型-->
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>

4.5 <environments>

 <environments>可以为MyBatis配置数据环境。

事务管理

<environments default="mysql">
  <environment id="mysql">
    <!-- JDBC:使用JDBC的提交和回滚 MANAGED:不做事务处理-->
    <transactionManager type="JDBC"></transactionManager>
  </environment>
</environments>

连接池

<environments default="mysql">
  <environment id="mysql">
    <transactionManager type="JDBC"></transactionManager>
    <!-- 连接池设置 -->
    <dataSource type="POOLED">
      <!-- 数据源设置... -->
    </dataSource>
  </environment>
</environments>

dataSource的type属性:

  • POOLED:使用连接池管理连接,使用MyBatis自带的连接池。
  • UNPOOLED:不使用连接池,直接由JDBC连接。
  • JNDI:由JAVAEE服务器管理连接,如果使用Tomcat作为服务器则使用Tomcat自带的连接池管理。

4.6 <mappers>

 <mappers>用于注册映射文件或持久层接口,只有注册的映射文件才能使用,共有四种方式都可以完成注册:

1、使用相对路径注册映射文件

    <mappers>
        <mapper resource="com/zj/mapper/UserMapper.xml"/>
    </mappers>

2、使用绝对路径注册映射文件

    <mappers>
      <mapper url="D:\Java\code\learnMyBatis\src\main\resources\com\zj\mapper\UserMapper.xml"></mapper>
    </mappers>

3、注册持久层接口(映射文件和持久层路径一样,文件名也一样)

    <!--注册映射文件(项目加载的时候先加载的是核心配置文件)-->
    <mappers>
        <mapper class="com.zj.mapper.UserMapper"/>
    </mappers>

4、注册一个包下的所有持久层接口

    <!--注册映射文件(项目加载的时候先加载的是核心配置文件)-->
    <mappers>
        <package name="com.zj.mapper"/>
    </mappers>

五、MyBatis映射文件

5.1<resultMap>

标签的作用的自定义映射关系。

MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同,当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。

此时有两种解决方案:

  1. Sql语句的查询字段起与POJO属性相同的别名。

    <!--查询全部老师-->
    <select id="findAllTeachers" resultType="Teacher">
        SELECT tid,tname as teacherName FROM teacher;
    </select>

   2、自定义映射关系

  • 在映射文件中,使用<resultMap>自定义映射关系
  • <select>标签中,使用resultMap属性代替resultType属性,使用自定义映射关系。
    <!--查询全部老师-->
    <resultMap id="findAllTeachersMap" type="Teacher"> <!--type:自定义的映射对象类型-->
        <id property="tid" column="tid"/><!--property实体类的属性,column字段名称-->
        <result property="teacherName" column="tname"/>
    </resultMap>
    <select id="findAllTeachers" resultMap="findAllTeachersMap">
        SELECT * FROM teacher;
    </select>

5.2 <sql>、<include>

 <sql>用来定义可重用的Sql片段,通过<include>引入该片段。如:Sql语句的查询字段起与POJO属性相同的别名,该Sql片段就可以重用。

    <sql id="sql">
        tid as tid,tname as teacherName
    </sql>
    <!--根据id查询老师-->
    <select id="findTeacherById" parameterType="int" resultType="Teacher">
        SELECT<include refid="sql"/> FROM teacher WHERE tid = #{tid}
    </select>

5.3 特殊字符处理

在Mybatis映射文件中尽量不要使用一些特殊字符,如:<>等。

我们可以使用符号的实体来表示:

符号实体
<&lt;
>&gt;
&&amp;
&apos;
&quot;
    <!--查询id大于某个值的老师-->
    <select id="findTeachersById2" parameterType="int" resultType="Teacher">
        SELECT <include refid="sql"/> FROM teacher WHERE tid &gt; #{tid}
    </select>

六、MyBatis动态Sql

6.1 <if>

 一个查询的方法的Sql语句不一定是固定的。比如电商网站的查询商品,用户使用不同条件查询,Sql语句就会添加不同的查询条件。此时就需要在方法中使用动态Sql语句。

<if>标签内的Sql片段在满足条件后才会添加,用法为:<if test="条件">。例如:根据不同条件查询用户:

1、持久层接口添加方法

    /*通用的用户查询*/
    List<User> findByCondition(User user);

2、映射文件

          <select id="findByCondition" parameterType="User" resultType="User">
              SELECT * FROM user where 1 = 1
              <if test="username != null and username.length() != 0">
                and username like #{username}
              </if>
              <if test="sex != null and sex.length() != 0">
                and sex = #{sex}
              </if>
              <if test="address != null and address.length() != 0">
                and address = #{address}
              </if>
          </select>

3、测试

    @Test
    public void TestFindByCondition(){
        User user = new User();
        user.setUsername("%张%");
        user.setSex("男");
        List<User> byCondition = mapper.findByCondition(user);
        for (User user1 : byCondition) {
            System.out.println(user1);
        }
    }

  1. if中的条件不能使用&&/||,而应该使用and/or

  2. if中的条件可以直接通过属性名获取参数POJO的属性值,并且该值可以调用方法。

  3. where后为什么要加1=1?

    任意条件都可能拼接到Sql中。如果有多个条件,从第二个条件开始前都需要加And关键字。加上1=1这个永久成立的条件,就不需要考虑后面的条件哪个是第一个条件,后面的条件前都加And关键字即可。

 6.2 <where>、<set>

 <where>可以代替sql中的where 1=1 和第一个and,更符合程序员的开发习惯,使用<where>后的映射文件如下:

    <update id="UpdateUser" parameterType="User">
        UPDATE user
        <set>
            <if test="username != null and username.length() != 0">
                username = #{username},
            </if>
            <if test="sex != null and sex.length() != 0">
                sex = #{sex},
            </if>
            <if test="address != null and address.length() != 0">
                address = #{address},
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>

<set>标签用在update语句中。借助<if>,可以只对有具体值的字段进行更新。<set>会自动添加set关键字,并去掉最后一个if语句中多余的逗号。

    <update id="UpdateUser" parameterType="User">
        UPDATE user
        <set>
            <if test="username != null and username.length() != 0">
                username = #{username},
            </if>
            <if test="sex != null and sex.length() != 0">
                sex = #{sex},
            </if>
            <if test="address != null and address.length() != 0">
                address = #{address},
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>

6.3 <choose>、<when>、<otherwise>

  • chose:父标签
  • when:相当于if...else if,只要有一个条件成立,其它的都不判断了
  • otherwise:相当于else,若所有条件都不成立,则执行otherwise
  • when至少设置一个,otherwise最多设置一个

1、持久层

    /*根据用户名查询用户:
    * 参数长度小于5使用模糊查询
    * 参数长度5~10使用精确查询
    * 否则返回id为1的用户*/
    List<User> findUserByName(String username);

2、映射文件

    <!--根据用户名查询用户-->
    <select id="findUserByName" parameterType="string" resultType="User">
           SELECT * FROM user
           <where>
               <choose>
                   <when test="username.length &lt; 5">
                   <bind name="likeName" value="'%'+username+'%'"/>
                       username like #{likeName}
                   </when>
                   <when test="username.length &lt; 10">
                       username = #{username}
                   </when>
                   <otherwise>
                       id = 1;
                   </otherwise>
               </choose>
           </where>
    </select>

6.4 foreach遍历数组

<foreach>类似JAVA中的for循环,可以遍历集合或数组。<foreach>有如下属性:

  • collection:遍历的对象类型
  • open:开始的sql语句
  • close:结束的sql语句
  • separator:遍历每项间的分隔符
  • item:表示本次遍历获取的元素,遍历List、Set、数组时表示每项元素,遍历map时表示键值对的值。
  • index:遍历List、数组时表示遍历的索引,遍历map时表示键值对的键。

我们使用<foreach>遍历数组进行批量删除。 

1、持久层接口添加方法

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

2、映射文件

    <!--批量删除-->
    <delete id="deleteBatch" parameterType="int">
        delete from user
            <where>
                <foreach open="id in (" collection="array" item="id" separator="," close=")">
                    #{id}
                </foreach>
            </where>
    </delete>

3、测试

    @Test
    public void TestDeleteBatch(){
        int[] batch = {1,2,3};
        mapper.deleteBatch(batch);
        sqlSession.commit();
    }

6.5 foreach遍历Collection

<foreach>遍历List和Set的方法是一样的,我们使用<foreach>遍历List进行批量添加。

1、持久层接口添加方法

    /*批量添加*/
    void insertBatch(List<User> users);

2、映射文件

    <!--批量添加-->
    <insert id="insertBatch" parameterType="User">
        insert into user values
        <foreach collection="list" item="user" separator=",">
            (default ,#{user.username},#{user.sex},#{user.address})
        </foreach>
    </insert>

3、测试

    @Test
    public void TestInsertBatch(){
        User user1 = new User(0,"顾田然","男","北京市");
        User user2 = new User(0,"孔丹秋","女","沈阳市");
        User user3 = new User(0,"唐宛凝","女","石家庄市");
        User user4 = new User(0,"陈波涛","男","牡丹江市");
        List<User> list =  new ArrayList<>();
        list.add(user1);
        list.add(user2);
        list.add(user3);
        list.add(user4);
        mapper.insertBatch(list);
        sqlSession.commit();
    }

 6.6 foreach遍历Map

我们使用<foreach>遍历Map进行多条件查询。

1、持久层接口添加方法

    /**
     * 多条件查询
     * @param map 查询的条件键值对 键:属性名 值:属性值
     * @return
     */
    List<User> findUser(@Param("queryMap") Map<String,Object> map);

2、映射文件

    <!--多条件查询-->
    <select id="findUser" parameterType="map" resultType="User">
        SELECT * FROM user
            <where>
                <foreach collection="queryMap" separator="and" index="key" item="value">
                     ${key} = #{value}
                </foreach>
            </where>
    </select>

3、测试

    @Test
    public void testFindUser(){
        Map<String,Object> map = new HashMap<>();
        map.put("sex","男");
        map.put("address","北京市");
        List<User> users = mapper.findUser(map);
        for (User user : users) {
            System.out.println(user);
        }
    }

七、MyBatis缓存

7.1 缓存介绍

 缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。

什么是缓存?

存在于内存中的一块数据。

缓存有什么作用?

减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。

什么样的数据使用缓存?

经常查询但不常改变的,改变后对结果影响不大的数据。

MyBatis缓存分为哪几类?

一级缓存和二级缓存

如何判断两次Sql是相同的?

  1. 查询的Sql语句相同
  2. 传递的参数值相同
  3. 对结果集的要求相同
  4. 预编译的模板Id相同

7.2 一级缓存

  • MyBatis一级缓存也叫本地缓存。SqlSession对象中包含一个Executor对象,Executor对象中包含一个PerpetualCache对象,在该对象存放一级缓存数据。

  • 由于一级缓存是在SqlSession对象中,所以只有使用同一个SqlSession对象操作数据库时才能共享一级缓存。

  • MyBatis的一级缓存是默认开启的,不需要任何的配置。

7.3 清除一级缓存

进行以下操作可以清空MyBatis一级缓存:

  1. SqlSession调用close():操作后SqlSession对象不可用,该对象的缓存数据也不可用。
  2. SqlSession调用clearCache()/commit():操作会清空一级缓存数据。
  3. SqlSession调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。

7.4 MyBatis二级缓存

  • MyBatis二级缓存也叫全局缓存。数据存放在SqlSessionFactory中,只要是同一个工厂对象创建的SqlSession,在进行查询时都能共享数据。一般在项目中只有一个SqlSessionFactory对象,所以二级缓存的数据是全项目共享的。

  • MyBatis一级缓存存放的是对象,二级缓存存放的是对象的数据。所以要求二级缓存存放的POJO必须是可序列化的,也就是要实现Serializable接口。

  • MyBatis二级缓存默认不开启,手动开启后数据先存放在一级缓存中,只有一级缓存数据清空后,数据才会存到二级缓存中。

SqlSession调用clearCache()无法将数据存到二级缓存中。

开启二级缓存

1、POJO类实现Serializable接口。

public class User implements Serializable {
  private int id;
  private String username;
  private String sex;
  private String address;
}

2、在MyBatis配置文件添加如下设置:

<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>

由于cacheEnabled默认值是true,所以该设置可以省略。

3、在映射文件添加<cache />标签,该映射文件下的所有方法都支持二级缓存。

<cache size="2048"/>

如果查询到的集合中对象过多,二级缓存只能缓存1024个对象引用。可以通过<cache />标签的size属性修改该数量。

八、MyBatis关联查询

MyBatis的关联查询分为一对一关联查询和一对多关联查询。

  • 查询对象时,将关联的另一个对象查询出来,就是一对一关联查询。
  • 查询对象时,将关联的另一个对象的集合查询出来,就是一对多关联查询。

例如有学生类和班级类:

一个学生对应一个班级,也就是学生类中有一个班级属性,这就是一对一关系。

一个班级对应多个学生,也就是班级类中有一个学生集合属性,这就是一对多关系。

实体类设计如下:

public class Student {
  private int sid;
  private String name;
  private int age;
  private String sex;
  private Classes classes;
  // 省略getter/setter/toString
}


public class Classes {
  private int cid;
  private String className;
  private List<Student> studentList;
  // 省略getter/setter/toString
}

数据库设计如下:

 8.1 一对一关联查询

 查询学生时,将关联的一个班级对象查询出来,就是一对一关联查询。

1、创建持久层接口

List<Student> findAllStudent();

2、映射文件

    <!--自定义映射关系-->
    <resultMap id="findAllStudentMap" type="Student">
          <id property="sid" column="sid"/>
          <result property="name" column="name"/>
          <result property="age" column="age"/>
          <result property="sex" column="sex"/>
        <!--一对一映射
           property:属性名;column:关联列名(两张表通过哪个字段相关联);javaType:对象类型
        -->
        <association property="classes" column="cid" javaType="Classes">
            <id property="cid" column="cid"/>
            <result property="className" column="className"/>
        </association>

    </resultMap>

    <select id="findAllStudent" resultMap="findAllStudentMap" >
        select * from student left join classes on student.classId = classes.cid;
    </select>

3、测试

    @Test
    public void testFindAllStudent()  {
        List<Student> allStudent = studentMapper.findAllStudent();
        for (Student student : allStudent) {
            System.out.println(student);
        }
    }

8.2 一对多关联查询

查询班级时,将关联的学生集合查询出来,就是一对多关联查询。

1、持久层

 List<Classes> finAllClasses();

2、映射文件

    <!--一对多查询-->
    <resultMap id="finAllClassesMap" type="Classes">
        <id property="cid" column="cid"/>
        <result property="className" column="className"/>
        <!--
        一对多关联映射
        property:属性名;column:关联列;ofType:集合泛型
        -->
        <collection property="studentList" column="classId" ofType="Student">
            <id property="sid" column="sid"/>
            <result property="name" column="name"/>
            <result property="age" column="age"/>
            <result property="sex" column="sex"/>
        </collection>
    </resultMap>
    <select id="finAllClasses" resultMap="finAllClassesMap">
         select * from classes left join student on classses.cid = student.classId
    </select>

3、测试

    @Test
    public void testFindAllClasses()  {
        List<Classes> classes = classesMapper.finAllClasses();
        for (Classes aClass : classes) {
            System.out.println(aClass);
        }
    }

 8.3多对多关系映射

MyBatis多对多关联查询本质就是两个一对多关联查询。

例如有老师类和班级类:

一个老师对应多个班级,也就是老师类中有一个班级集合属性。

一个班级对应多个老师,也就是班级类中有一个老师集合属性。

实体类设计如下:

public class Teacher {
  private Integer tid;
  private String tname;
  private List<Classes> classes;
  // 省略getter/setter/toString
}


public class Classes {
  private Integer cid;
  private String className;
  private List<Student> studentList;
  private List<Teacher> teacherList;
  // 省略getter/setter/toString
}

在数据库设计中,需要建立中间表,双方与中间表均为一对多关系。

 1、持久层

List<Teacher> findAllTeachers();

2、映射文件

    <!--多对多关联-->
    <resultMap id="findAllTeachersMap" type="Teacher">
        <id property="tid" column="tid"/>
        <result property="tname" column="tname"/>
        <!--column:主表的关联键-->
        <collection property="classesList" column="tid" ofType="Classes">
            <id property="cid" column="cid"/>
            <result property="className" column="className"/>
        </collection>
    </resultMap>
    <select id="findAllTeachers" resultMap="findAllTeachersMap" >
        select * from teacher
        left join classes_teacher on teacher.tid = classes_teacher.tid
        left join classes on classes_teacher.cid = classes.cid
    </select>

3、测试

    @Test
    public void testFindAllTeachers()  {
        List<Teacher> allTeachers = teacherMapper.findAllTeachers();
        for (Teacher allTeacher : allTeachers) {
            System.out.println(allTeacher);
        }
    }

 九、MyBatis分解式查询

在MyBatis多表查询中,使用连接查询时一个Sql语句就可以查询出所有的数据。如:

# 查询班级时关联查询出学生
select *
  from classes
  left join student
  on student.classId = classes.cid

也可以使用分解式查询,即将一个连接Sql语句分解为多条Sql语句,如:

# 查询班级时关联查询出学生
select * from classes;
select * from student where classId = 1;
select * from student where classId = 2; 

这种写法也叫N+1查询

连接查询:

  • 优点:降低查询次数,从而提高查询效率。
  • 缺点:如果查询返回的结果集较多会消耗内存空间。

N+1查询:

  • 优点:结果集分步获取,节省内存空间。
  • 缺点:由于需要执行多次查询,相比连接查询效率低。

9.1 一对多分解式查询 

我们以查询班级时关联查询出学生为例,使用N+1查询: 

 1、持久层

public interface ClassesMapper {
  // 查询所有班级
  List<Classes> findAll();
}


public interface StudentMapper {
  // 根据班级Id查询学生
  List<Student> findByClassId(int classId);
}

2、映射文件

    <!--分解查询-->
    <resultMap id="findAllClasses2Map" type="Classes">
        <id property="cid" column="cid"/>
        <result property="className" column="className"/>
        <!--调用从表的查询方法.column:将哪个列的值作为select语句的参数-->
        <collection property="studentList" ofType="Student" select="com.zj.mapper.StudentMapper.findByClassId" column="cid"/>
    </resultMap>
    <select id="findAllClasses2" resultMap="findAllClasses2Map">
        SELECT * from classes;
    </select>


    <!--分解查询,根据classId查学生-->
    <select id="findByClassId" parameterType="int" resultType="Student">
        SELECT * from student where classId = #{classId}
    </select>

3、测试

    /*分解查询*/
    @Test
    public void testFindAllClasses2(){
        List<Classes> allClasses2 = classesMapper.findAllClasses2();
        for (Classes classes : allClasses2) {
            System.out.println(classes);
        }
    }

 9.2 一对一分解式查询

根据学生id查询学生的信息,查询学生时关联查询出班级也可以使用分解式查询,首先将查询语句分开:

select * from student;
select * from classes where cid = ?;

1、持久层

public interface StudentMapper {
   // 查询所有学生
  List<Student> findAll();
}


public interface ClassesMapper {
  // 根据ID查询班级
  Classes findByCid(int cid);
}

2、映射文件

    <!--分解式查询学生和学生所在的班级的信息-->
    <resultMap id="findAllStudent2Map" type="Student">
        <id property="sid" column="sid"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <association property="classes" select="com.zj.mapper.ClassesMapper.findClassById" column="classId"/>
    </resultMap>
    <select id="findAllStudent2" resultMap="findAllStudent2Map">
        select * from student;
    </select>


    <!--分解查询根据班级id查询班级信息-->
    <select id="findClassById" parameterType="int" resultType="Classes">
        select * from classes where cid = #{cid};
    </select>

3、测试

    @Test
    public void testFindAllStudent()  {
        List<Student> allStudent = studentMapper.findAllStudent();
        for (Student student : allStudent) {
            System.out.println(student);
        }
    }

 9.3 延迟加载

分解式查询又分为两种加载方式:

  • 立即加载:在查询主表时就执行所有的Sql语句。
  • 延迟加载:又叫懒加载,首先执行主表的查询语句,使用从表数据时才触发从表的查询语句。

延迟加载在获取关联数据时速度较慢,但可以节约资源,即用即取。

开启延迟加载

设置所有的N+1查询都为延迟加载:

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>
  • 设置某个方法为延迟加载:

    <association><collection>中添加fetchType属性设置加载方式。lazy:延迟加载;eager:立即加载。

测试延迟加载

十、MyBatis注解开发

  1. 创建maven工程,引入依赖

  2. 创建mybatis核心配置文件SqlMapConfig.xml

  3. 将log4j.properties文件放入resources中,让控制台打印SQL语句。

  4. 创建实体类

  5. 创建持久层接口,并在接口方法上定义Sql语句

public interface UserMapper {
  @Select("select * from user")
  List<User> findAll();
}

由于注解在方法上方,而方法中就有参数类型和返回值类型,所以使用注解开发不需要定义参数类型和返回值类型

6、在核心配置文件注册持久层接口,由于没有映射文件,所以只能采用注册接口或注册包的方法。

    <!--基于注解开发的话,没有映射文件就注册接口-->
    <mappers>
       <package name="com.zj.mapper"/>
    </mappers>

7、测试方法

    @Test
    public void testFindAllUsers(){
        List<User> allUsers = userMapper.findAllUsers();
        for (User allUser : allUsers) {
            System.out.println(allUser);
        }
    }

10.1 基于注解实现增删改查 

    @Select("select * from user")
    List<User> findAllUsers();

    @SelectKey(keyColumn = "id",keyProperty = "id",resultType = int.class,before = false,
              statement = "select LAST_INSERT_ID()")
    @Insert("insert into user values (default, #{username}, #{sex},#{address})")
    void addUser(User user);

    @Delete("delete from user where id = #{id}")
    void removeUser(int id);

    @Update("update user set username = #{username}, sex = #{sex}, address = #{address}" +
            "where id = #{id}")
    void updateUser(User user);

    @Select("select * from user where username like #{username}")
    List<User> findUserLike(String username);

10.2 动态sql

 

 MyBatis注解开发中有两种方式构建动态Sql:

使用脚本标签(了解即可千万别用,又臭又长)

将Sql嵌套在<script>内即可使用动态Sql标签:

// 根据任意条件查询
@Select("<script>" +
    "  select * from user\n" +
    "     <where>\n" +
    "       <if test=\"username != null and username.length() != 0\">\n" +
    "         username like #{username}\n" +
    "       </if>\n" +
    "       <if test=\"sex != null and sex.length() != 0\">\n" +
    "         and sex = #{sex}\n" +
    "       </if>\n" +
    "       <if test=\"address != null and address.length() != 0\">\n" +
    "         and address = #{address}\n" +
    "       </if>\n" +
    "     </where>" +
    "</script>")
List<User> findByCondition(User user);

在方法中构建动态Sql

在MyBatis中有@SelectProvider@UpdateProvider@DeleteProvider@InsertProvider注解。当使用这些注解时将不在注解中直接编写SQL,而是调用某个类的方法来生成SQL。

1、创建类构建sql语句

package com.zj.provider;

import com.zj.pojo.User;

/*构建sql*/
public class UserProvider {
        //生成根据任意条件查询的sql语句
    public String findByConditionSql(User user){
        StringBuffer sb = new StringBuffer("SELECT * FROM  user WHERE 1=1");
        if (user.getUsername() != null && user.getUsername().length() > 0){
            sb.append(" and username = #{username}");
        }
        if (user.getSex() != null && user.getSex().length() > 0){
            sb.append(" and sex = #{sex}");
        }
        if (user.getAddress() != null && user.getAddress().length() > 0){
            sb.append(" and address = #{address}");
        }
        return sb.toString();
    }
}

2、持久层

    /*根据任意给定条件查询用户,使用某个类构建sql*/
    @SelectProvider(type = UserProvider.class,method = "findByConditionSql")
    List<User> findByConditionSql(User user);

3、测试

    @Test
    public void testFindByConditionSql(){
        User user = new User();
        user.setUsername("顾田然");
        List<User> users = userMapper.findByConditionSql(user);
        for (User user1 : users) {
            System.out.println(user1);
        }
    }

 10.3 自定义映射

当POJO属性名与数据库列名不一致时,需要自定义实体类和结果集的映射关系,在MyBatis注解开发中,使用@Results定义并使用自定义映射,使用@ResultMap使用自定义映射,用法如下:

    /*自定义映射*/
    @Results(id = "userMapper",value = {
            @Result(id = true,property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "sex",column = "sex"),
            @Result(property = "address",column = "address")
    })
    @Select("select * from user")
    List<User> findAllUsers();

10.4 基于注解开启二级缓存

 1、POJO类实现Serializable接口。

2、在持久层接口上方加注解@CacheNamespace(blocking=true),该接口的所有方法都支持二级缓存。

@CacheNamespace(blocking = true)
public interface UserMapper {

……
}

10.5 基于注解实现一对一关联查询

 在MyBatis的注解开发中对于多表查询只支持分解查询(N+1),不支持连接查询。

1、创建实体类

public class Student {
  private int sid;
  private String name;
  private int age;
  private String sex;
  private Classes classes;
  // 省略getter/setter/toString
}


public class Classes {
  private int cid;
  private String className;
  private List<Student> students;
  // 省略getter/setter/toString
}

2、持久层

package com.zj.mapper;

import com.zj.pojo.Student;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;

import java.util.List;

public interface StudentMapper {


    //查询全部学生
    @Results(id = "StudentMapper" ,value = {
            @Result(id = true,property = "sid",column = "sid"),
            @Result(property = "name",column = "name"),
            @Result(property = "age",column = "age"),
            @Result(property = "sex",column = "sex"),
            //调用查询时传入的参数是哪列
            @Result(property = "classes",column = "classId",
                    one = @One(select = "com.zj.mapper.ClassesMapper.findClassById",
                    fetchType = FetchType.EAGER)
            )
    })
    @Select("select * from Student")
    List<Student> findAllStudent();
}




package com.zj.mapper;

import com.zj.pojo.Classes;
import org.apache.ibatis.annotations.Select;


public interface ClassesMapper {
    //根据班级cid查询班级信息
    @Select("select * from classes where cid = #{cid}")
    Classes findClassById(int cid);;
}

3、测试

    @Test
    public void testStudentMapper() {
        List<Student> allStudent = studentMapper.findAllStudent();
        for (Student student : allStudent) {
            System.out.println(student);
        }
    }

10.6 基于注解实现一对多关联查询 

查询班级信息的时候将该班级下的学生一起查出来。

1、持久层

    //查询全部班级(一对多一般使用懒加载)
    @Results(id = "classesMap",value = {
            @Result(id = true,property = "cid",column = "cid"),
            @Result(property = "className",column = "className"),
            @Result(property = "studentList",column = "cid",
            many = @Many(select="com.zj.mapper.StudentMapper.findStudentByCid",
            fetchType = FetchType.LAZY))
    })
    @Select("select * from classes")
    List<Classes> findAllClasses();



    //根据班级id查询学生
    @Select("select * from student where classId = #{classId}")
    Student findStudentByCid(int classId);



2、测试

    @Test
    public void testFindAllClasses(){
        List<Classes> allClasses = classesMapper.findAllClasses();
        for (Classes allClass : allClasses) {
            System.out.println(allClass);
        }
    }

10.7 注解和映射文件对比

 MyBatis中更推荐使用映射文件开发,Spring、SpringBoot更推荐注解方式。具体使用要视项目情况而定。它们的优点对比如下:

映射文件:

  • 代码与Sql语句是解耦的,修改时只需修改配置文件,无需修改源码。
  • Sql语句集中,利于快速了解和维护项目。
  • 级联查询支持连接查询和分解查询两种方式,注解开发只支持分解查询。

注解:

  • 配置简单,开发效率高。
  • 类型安全,在编译期即可进行校验,不用等到运行时才发现错误。

十一、PageHelper分页插件

 开发过程中如果要进行分页查询,需要传入页数和每页条数。返回页面数据,总条数,总页数,当前页面,每页条数等数据。此时使用PageHelper插件可以快速帮助我们获取这些数据。

PageHelper是一款非常好用的开源免费的Mybatis第三方分页插件。使用该插件时,只要传入分页参数,即可自动生成页面对象。我们使用该插件分页查询所有用户:

1、引入依赖

<!-- PageHelper -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.0</version>
</dependency>

2、Mybatis配置文件中配置PageHelper插件

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 设置数据库类型-->
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>

3、测试

    @Test
    public void testFindAllUsers(){
        //1、设置分页参数
        Page<Object> objects = PageHelper.startPage(1, 3);
        //查询数据库
        List<User> allUsers = userMapper.findAllUsers();
        //封装查询结果生成页面对象
        PageInfo<User> pageInfo = new PageInfo(allUsers);
        //打印页面数据
        List<User> list = pageInfo.getList();
        System.out.println("结果集:"+list);
        System.out.println("总条数:"+pageInfo.getTotal());
        System.out.println("总页数:"+pageInfo.getPages());
        System.out.println("当前页:"+pageInfo.getPageNum());
        System.out.println("每页条数:"+pageInfo.getSize());
    }

 十二、MyBatis Generator

MyBatis Generator(MBG)是MyBatis官方提供的代码生成器。它可以根据数据库的表结构自动生成POJO类、持久层接口与映射文件,极大减少了代码的编写量,提高开发效率。

MBG可以作为项目引入使用,也可以作为Maven插件使用,其中作为Maven插件使用更加方便快捷。

1、准备数据库表

 2、在pom文件中配置MBG插件

    <build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.2</version>
                <!--MBG配置-->
                <configuration>
                    <!--配置文件的位置-->
                    <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
                    <!--运行显示详情-->
                    <verbose>true</verbose>
                    <!--允许文件覆盖-->
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>

3、编写MBG配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">


<generatorConfiguration>
    <!-- jdbc的jar包位置,插件需要连接数据库 -->
    <classPathEntry location="D:\Java\apache-maven-3.8.3\repositories\mysql\mysql-connector-java\8.0.26\mysql-connector-java-8.0.26.jar"/>

    <context id="default" targetRuntime="MyBatis3">
        <!-- 是否去除自动生成的注释-->
        <commentGenerator>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--数据库连接参数-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="123456">
        </jdbcConnection>


        <!-- 类型处理器,在数据库类型和java类型之间的转换控制-->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>


        <!-- targetProject:POJO类路径  targetProject:生成的POJO类的包(需要提前创建包)-->
        <javaModelGenerator targetProject="src/main/java" targetPackage="com.zj.pojo">
            <!-- 是否生成子包 -->
            <property name="enableSubPackages" value="false"/>
            <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>


        <!-- targetProject:配置文件路径  targetPackage:生成映射文件的位置(要提前创建文件目录) -->
        <sqlMapGenerator targetProject="src/main/resources" targetPackage="com.zj.mapper">
            <!-- 是否生成子包 -->
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>


        <!-- targetPackage:JAVA类路径 targetProject:生成的持久层接口包 -->
        <javaClientGenerator targetProject="src/main/java" targetPackage="com.zj.mapper" type="XMLMAPPER">
            <!-- 是否生成子包 -->
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>


        <!-- 数据库表(根据哪些表生成,可以写多个table标签),表名不要和其他库中的表名一样 -->
        <table tableName="product"></table>

    </context>
</generatorConfiguration>

4、双击运行插件,自动生成POJO,持久层接口,映射文件:

 

 

  • Product.java:POJO类

  • ProductMapper.java:持久层接口

  • ProductMapper.xml:映射文件

  • ProductExample.java:查询扩展类,该类可以构造复杂的查询条件。

    • Criterion:代表一个字段。
    • GeneratedCriteria:抽象类,生成查询条件的工具。
    • Criteria:GeneratedCriteria的子类,生成查询条件的工具。

12.1 增删改方法

 注意在生成插件后还要搭建mybatis,再添加上配置文件。

    //添加
    @Test
   public void testAdd(){
       Product product = new Product(0,"英特尔(Intel) i7-13700K 13代 酷睿 处理器",2999.0);
       int insert = productMapper.insert(product);
       sqlSession.commit();
   }

   //修改
    @Test
    public void testUpdate(){
        Product product = new Product();
        product.setId(3);
        product.setProductname("小天才电话手表");
        product.setPrice(499.0);
        int i = productMapper.updateByPrimaryKey(product);
        sqlSession.commit();
    }

    //删除
    @Test
    public void testDelete(){
        int i = productMapper.deleteByPrimaryKey(2);
        sqlSession.commit();
    }

12.2 查询方法

    //根据id查询
    @Test
    public void testFindById(){
        Product product = productMapper.selectByPrimaryKey(1);
        System.out.println(product);
    }

    //查询全部
    @Test
    public void testFindAll(){
        /*除了根据id查询之外其他的查询方法都需要使用查询扩展对象来构建查询条件*/
        ProductExample productExample = new ProductExample();
        /*查询全部不需要构建查询条件*/
        List<Product> products = productMapper.selectByExample(productExample);
        for (Product product : products) {
            System.out.println(product);
        }
    }
    //根据名称查询
    @Test
    public void testFindByName(){
        /*构建查询条件*/
        ProductExample productExample = new ProductExample();
        ProductExample.Criteria criteria = productExample.createCriteria();
        criteria.andProductnameLike("%华为Mate40%");
        /*查询*/
        List<Product> products = productMapper.selectByExample(productExample);
        for (Product product : products) {
            System.out.println(product);
        }
    }

12.3复杂查询

    //多条件的and查询
    @Test
    public void testFindAnd(){
        /*构建查询条件*/
        ProductExample productExample = new ProductExample();
        ProductExample.Criteria criteria = productExample.createCriteria();
        criteria.andProductnameLike("%华为%");
        criteria.andPriceBetween(3000.0,10000.0);
        List<Product> products = productMapper.selectByExample(productExample);
        for (Product product : products) {
            System.out.println(product);
        }
    }
    //多条件的or查询
    @Test
    public void testFindOr(){
        /*构建查询条件1*/
        ProductExample productExample = new ProductExample();
        ProductExample.Criteria criteria1 = productExample.createCriteria();
        criteria1.andProductnameLike("%华为%");
        /*构建查询条件2*/
        ProductExample.Criteria criteria2 = productExample.createCriteria();
        criteria2.andPriceBetween(8000.0,10000.0);
        /*创建or关系(括号中只放第二个)*/
        productExample.or(criteria2);
        /*查询*/
        List<Product> products = productMapper.selectByExample(productExample);
        for (Product product : products) {
            System.out.println(product);
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张小猿ε٩(๑> ₃ <)۶ з

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值