数据持久层框架:MyBatis

前言

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJOPlain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.15</version>
</dependency>
<!--数据库驱动依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.22</version>
</dependency>

入门

SqlSessionFactory

SqlSessionFactoryMyBatis 中非常重要的一个接口,它负责创建 SqlSession 实例。在 MyBatis 中,SqlSession 用于执行 SQL 命令并返回结果,而 SqlSessionFactory 则用于创建和配置 SqlSession 实例。

MyBatis 中,使用 SqlSessionFactory 的典型流程如下:

  1. 配置 SqlSessionFactory:通常通过 XML 文件或 Java 代码来配置 SqlSessionFactory,包括配置数据源、映射文件等。

mybatis-config.xml文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
        	<!--事务管理-->
            <transactionManager type="JDBC"/>
            <!--数据源-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
    	<!--加载配置文件-->
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

UserMapper.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">
<!--必填 namespace定义了命名空间,也就是映射的类-->
<mapper namespace="com.example.mybatisstudy.dao.UserDao">
  <select id="selectByUser" resultType="com.example.mybatisstudy.User">
    select * from user;
  </select>
</mapper>

映射DAO层代码:

public interface UserDao {
    List<User> selectByUser();
}

当然,还有很多可以在 XML 文件中配置的选项,上面的示例仅罗列了最关键的部分。 environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。

注意 XML 头部的声明,它用来验证 XML 文档的正确性。

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
  1. 创建 SqlSessionFactory 实例:根据配置信息创建 SqlSessionFactory 实例。
  2. 使用 SqlSessionFactory 创建 SqlSession:通过 SqlSessionFactory 创建 SqlSession 实例。
  3. 使用 SqlSession 执行 SQL 命令:通过 SqlSession 实例执行需要的 SQL 命令,如查询、插入、更新、删除等。
  4. 关闭 SqlSession:在完成数据库操作后,需要关闭 SqlSession

以下是一个简单的示例代码,演示了如何使用 SqlSessionFactorySqlSession

public class User {
    private Integer id;
    private String name;
    private String age;
    
    // setter and getter
}
public class Test {
    public static void main(String[] args) throws IOException {
        // 创建配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

        // 根据配置文件创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 创建 SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 执行 SQL 命令
        List<User> userList = sqlSession.selectList("selectByUser");
        userList.forEach(item-> System.out.println(item.toString()));
        // 关闭 SqlSession
        sqlSession.close();
    }
}

如图所示

在这里插入图片描述

  • 不使用 XML 构建 SqlSessionFactory

如果你更愿意直接从 Java 代码而不是 XML 文件中创建配置,或者想要创建你自己的配置构建器,MyBatis 也提供了完整的配置类,提供了所有与 XML 文件等价的配置项。

public class Test {
    public static void main(String[] args) throws IOException {
        DataSource dataSource = new PooledDataSource("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf-8&useSSL=false", "root", "123456");

        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development", transactionFactory, dataSource);

        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(environment);
        configuration.addMapper(UserMapper.class);

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> selectByUser = sqlSession.selectList("selectByUser");
        selectByUser.forEach(item-> System.out.println(item.toString()));
        // 关闭 SqlSession
        sqlSession.close();
    }
}

*Mapper.xmlJava代码要放一起,且名称一致。

在这里插入图片描述

pom文件需要配置,在<build>标签里加入资源路径。

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>classpath</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

注意该例中,configuration 添加了一个映射器类(mapper class)。映射器类是 Java 类,它们包含 SQL 映射注解从而避免依赖 XML 映射文件。有鉴于此,如果存在一个同名 XML 映射文件,MyBatis 会自动查找并加载它(在这个例子中,基于类路径和 UserMapper.class 的类名,会加载 UserMapper.xml)。

不过,由于 Java 注解的一些限制以及某些 MyBatis 映射的复杂性,要使用大多数高级映射(比如:嵌套联合映射),仍然需要使用 XML 映射文件进行映射。

SqlSession

SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

注:新增、删除、修改,操作需要调用commit()方法提交事务。

  • 新增

通过SqlSessioninsert()方法进行插入操作。

public class Test {
    public static void main(String[] args) throws IOException {
        DataSource dataSource = new PooledDataSource("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf-8&useSSL=false", "root", "123456");

        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development", transactionFactory, dataSource);

        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(environment);
        configuration.addMapper(UserMapper.class);
        
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        int insert = sqlSession.insert("insert", new User(6, "e", "22"));//参数一:调用方法,参数二:参数
        //提交数据持久化
        sqlSession.commit();
        // 关闭 SqlSession
        sqlSession.close();

    }
}
  • 删除

调用delete()方法执行删除操作。

public class Test {
    public static void main(String[] args) throws IOException {
        //省略代码... ...
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        int delete = sqlSession.delete("delete", 6);//参数一:调用方法,参数二:参数
        //提交数据持久化
        sqlSession.commit();
        // 关闭 SqlSession
        sqlSession.close();

    }
}
  • 修改

调用update()方法执行修改操作。

public class Test {
    public static void main(String[] args) throws IOException {
        //省略代码... ...
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        int delete = sqlSession.update("update", new User(5,"ee","22"));//参数一:调用方法,参数二:参数
        //提交数据持久化
        sqlSession.commit();
        // 关闭 SqlSession
        sqlSession.close();

    }
}

除此之外,如果查询单条记录可以调用selectOne()方法

public class Test {
    public static void main(String[] args) throws IOException {
        //省略代码... ...
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("selectOne", 5);//参数一:调用方法,参数二:参数
        System.out.println(user.toString());
        // 关闭 SqlSession
        sqlSession.close();
    }
}

但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 UserMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。

public class Test {
    public static void main(String[] args) throws IOException {
        DataSource dataSource = new PooledDataSource("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf-8&useSSL=false", "root", "123456");

        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development", transactionFactory, dataSource);

        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(environment);
        configuration.addMapper(UserMapper.class);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectByUser();
        // 关闭 SqlSession
        sqlSession.close();
    }
}
  • 探究已映射的 SQL 语句

一个语句既可以通过 XML 定义,也可以通过注解定义,在一个 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="com.example.mybatisstudy.dao.UserMapper">
  <select id="selectByUser" resultType="com.example.mybatisstudy.User">
    select * from user;
  </select>
</mapper>

它在命名空间 “com.example.mybatisstudy.dao.UserMapper” 中定义了一个名为 “selectByUser” 的映射语句,这样你就可以用全限定名 “com.example.mybatisstudy.dao.UserMapper.selectBlog” 来调用映射语句了,就像上面例子中那样:

public class Test {
    public static void main(String[] args) throws IOException {
        //省略代码... ...
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectByUser();
        // 关闭 SqlSession
        sqlSession.close();
    }
}

在之前版本的 MyBatis 中,命名空间(Namespaces) 的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis,防止产生“短名称不唯一”的错误。

它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。

public interface UserMapper {
    @Select("select * from user where id = #{id}")
    User selectOne(Integer id);
}

除此之外,还有@Insert注解、 @Delete注解、 @Update注解,示例代码如下:

public interface UserMapper {

    @Insert("insert into user(id, name, age) values (#{id},#{name},#{age})")
    int insert(User user);

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

    @Update("UPDATE `spring_data`.`user` SET `name` = #{name}, `age` = #{age} WHERE `id` = #{id}")
    int update(User user);
}

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

作用域(Scope)和生命周期

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。

  • SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

  • SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

  • SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

public class Test {
    public static void main(String[] args) throws IOException {
        //省略部分代码... ...
        try(SqlSession sqlSession = sqlSessionFactory.openSession();) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            int user = mapper.delete(6);
            sqlSession.commit();
        }
    }
}

配置

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。配置文档的顶层结构如下:

在这里插入图片描述

properties(属性)

你既可以在典型的 Java 属性文件中配置这些属性,properties也可以在外部进行配置,并可以进行动态替换。

applicationg.properties文件

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

配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="application.properties"></properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <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>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

同样也可以在 properties 元素的子元素中设置。设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。例如:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <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>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

如果一个properties在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  1. 首先读取在 properties 元素体内指定的属性。
  2. 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  3. 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。要启用这个特性,需要添加一个特定的属性来开启这个特性。

    <properties>
        <!-- 启用默认值特性 -->
        <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <!--忽略密码属性-->
    </properties>

使用如下:

    <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password:123456}"/>
    </dataSource>

settings(设置)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。如图所示。

在这里插入图片描述
<settings>标签中设置配置项,示例代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings>
        <setting name="cacheEnabled" value="true"/>
        <!-- 更多配置... -->
    </settings>

    <environments default="development"></environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

typeAliases(别名)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

mybatis-config.xml配置文件中使用<typeAliases>标签设置别名:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases>
        <typeAlias type="com.example.mybatisstudy.User" alias="user"></typeAlias>
    </typeAliases>
    
    <environments default="development"></environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

UserMapper.xml配置文件中resultTypeparameterType可以使用别名定义,而不用全限定类名书写。

<?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.example.mybatisstudy.dao.UserMapper">

  <select id="selectByUser" resultType="user">
    select * from user;
  </select>
  <insert id="insert" parameterType="user">
    insert into user(id, name, age) values (#{id},#{name},#{age});
  </insert>
</mapper>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java对象,比如:

    <typeAliases>
        <package name="com.example.mybatisstudy.pojo"/>
    </typeAliases>

每一个在包中的 Java 对象,在没有注解的情况下,会使用对象的首字母小写的非限定类名来作为它的别名。 比如 com.example.mybatisstudy.pojo.User的别名为 user;若有注解,则别名为其注解值。见下面的例子:

@Alias("user2")
public class User {
}

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

在这里插入图片描述

typeHandlers(类型处理器)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。如图描述了一些默认的类型处理器。

在这里插入图片描述
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。比如:

@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}

mybatis-config.xml中使用<typeHandlers>标签进行配置,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers>
        <typeHandler handler="com.example.mybatisstudy.ExampleTypeHandler"/>
    </typeHandlers>
    
    <environments default="development"></environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

使用上述的类型处理器将会覆盖已有的处理器。 要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。

objectFactory(对象工厂)

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:

public class ExampleObjectFactory extends DefaultObjectFactory {
  @Override
  public <T> T create(Class<T> type) {
    return super.create(type);
  }

  @Override
  public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }

  @Override
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }

  @Override
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }}

mybatis-config.xml中使用<objectFactory>标签进行配置,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory type="com.example.mybatisstudy.ExampleObjectFactory">
        <property name="someProperty" value="100"/>
    </objectFactory>
    
    <environments default="development"></environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

ObjectFactory 可以被用户自定义以适应特定需求。当您需要控制 MyBatis 如何创建结果对象时,可以配置 ObjectFactory。另外,setProperties() 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties() 方法,创建结果对象的过程中可以使用这些属性来进行定制化处理。

plugins(插件)

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

(1)Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

(2)ParameterHandler (getParameterObject, setParameters)

(3)ResultSetHandler (handleResultSets, handleOutputParameters)

(4)StatementHandler (prepare, parameterize, batch, update, query)

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

@Intercepts({@Signature(
        type= Executor.class,
        method = "query",//增删改使用 update
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //处理前
        System.out.println("1");
        Object returnObject = invocation.proceed();
        //处理后
        System.out.println("2");
        return returnObject;
    }

    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

通过@Intercepts 注解,用于拦截 Executor 接口的 query 方法,该方法接受四个类型参数: MappedStatement 类型和Object 类型、RowBounds类型和ResultHandler类型。

注意:Executor.class的包是org.apache.ibatis.executor.Executor,否则会报错。

mybatis-config.xml中使用<plugins>标签进行配置,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory></objectFactory>
    
    <plugins>
        <plugin interceptor="com.example.mybatisstudy.ExamplePlugin"></plugin>
    </plugins>
    
    <environments default="development"></environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

执行结果如图

在这里插入图片描述

environments(环境)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推。

mybatis-config.xml中使用<environments >标签进行配置,比如:有两个配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory></objectFactory>
    
    <plugins></plugins>
    
    <environments default="development">
		<environment id="development">
        </environment>
	</environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory></objectFactory>
    
    <plugins></plugins>
    
    <environments default="prod">
		<environment id="prod">
        </environment>
	</environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。

 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,"prod");

如果忽略了环境参数,那么将会加载默认环境(development)。

注意一些关键点:

(1)默认使用的环境 ID(比如:default="development")。

(2)每个 environment 元素定义的环境 ID(比如:id="development")。

(3)事务管理器的配置(比如:type="JDBC")。

(4)数据源的配置(比如:type="POOLED")。

默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID

  • transactionManager(事务管理器)

MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

(1)JDBC – 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。然而,对于某些驱动程序来说,启用自动提交不仅是不必要的,而且是一个代价高昂的操作。因此,从 3.5.10 版本开始,你可以通过将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过这个步骤。

<environment>标签中使用<transactionManager>子标签进行配置,例如:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory></objectFactory>
    
    <plugins></plugins>
    
    <environments default="development">
		<environment id="development">
			<transactionManager type="JDBC">
	            <property name="skipSetAutoCommitOnClose" value="true"/>
	        </transactionManager>
        </environment>
	</environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

(2)MANAGED – 这个配置几乎没做什么。

如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

  • dataSource(数据源)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

<environment>标签中使用<dataSource>子标签进行配置。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory></objectFactory>
    
    <plugins></plugins>
    
    <environments default="development">
		<environment id="development">
			<transactionManager type="JDBC"></transactionManager>
            <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>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

(1)UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

  1. driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
  2. url – 这是数据库的 JDBC URL 地址。
  3. username – 登录数据库的用户名。
  4. password – 登录数据库的密码。
  5. defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
  6. defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。

作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:driver.encoding=UTF8
这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8encoding 属性给数据库驱动。

(2)POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  1. poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
  2. poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
  3. poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
  4. poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
  5. poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过
  6. poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
  7. poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是**“NO PING QUERY SET”**,这会导致多数数据库驱动出错时返回恰当的错误消息。
  8. poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
  9. poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabledtrue 时适用)。

(3)JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:

  1. initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
  2. data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

databaseIdProvider(数据库厂商标识)

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要在mybatis-config.xml中使用<databaseIdProvider>标签进行配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory></objectFactory>
    
    <plugins></plugins>
    
    <environments default="development">
		<environment id="development">
			<transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED"></dataSource>
        </environment>
	</environments>
    <databaseIdProvider type="DB_VENDOR">
            <property name="SQL Server" value="sqlserver"/>
            <property name="DB2" value="db2"/>
            <property name="Oracle" value="oracle" />
    </databaseIdProvider>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

mappers(映射器)

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<!--省略其他标签详细配置-->
    <properties></properties>

    <settings></settings>
    
    <typeAliases></typeAliases>
    
    <typeHandlers></typeHandlers>
    
    <objectFactory></objectFactory>
    
    <plugins></plugins>
    
    <environments default="development"></environments>
    
    <databaseIdProvider type="DB_VENDOR"></databaseIdProvider>
    
    <mappers>
    <mappers>
        <!-- 使用相对于类路径的资源引用 -->
        <mapper resource="/config/UserMapper.xml"/>
        <!-- 使用完全限定资源定位符(URL-->
        <mapper resource="file:///var/mappers/UserMapper.xml"/>
        <!-- 使用映射器接口实现类的完全限定类名 -->
        <mapper class="com.example.mybatisstudy.UserMapper"/>
        <!-- 将包内的映射器接口全部注册为映射器 -->
        <package name="com.example.mybatisstudy.builder"/>
    </mappers>
    </mappers>
</configuration>

XML 映射器

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

select

查询语句是 MyBatis 中最常用的元素之一,MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单的。比如:

  <select id="selectOne" parameterType="int" resultType="hashmap">
    select * from user where `id` = #{id};
  </select>

这个语句名为 selectOne,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。

注意参数符号:#{id},告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中。

// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";

select 元素允许你配置很多属性来配置每条语句的行为细节。

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

Select 元素的属性如图
在这里插入图片描述

insert, update 和 delete

数据变更语句 insertupdatedelete 的实现非常接近:

  <insert id="insert" parameterType="com.example.mybatisstudy.pojo.User">
    insert into user(id, name, age) values (#{id},#{name},#{age});
  </insert>

  <delete id="delete">
    delete from user where id = #{id};
  </delete>

  <update id="update"  parameterType="com.example.mybatisstudy.pojo.User">
    UPDATE `spring_data`.`user` SET `name` = #{name}, `age` = #{age} WHERE `id` = #{id};
  </update>

insertupdatedelete 元素也可以配置很多属性的行为细节。

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

详细属性如图
在这里插入图片描述
如果你的数据库支持自动生成主键的字段(比如 MySQLSQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性。

  <insert id="insert" parameterType="com.example.mybatisstudy.pojo.User" 
  	useGeneratedKeys="true" keyProperty="id">
    insert into user(name, age) values (#{name},#{age});
  </insert>

如果你的数据库还支持批量插入, 你也可以传入一个数组或集合。

  <insert id="batchInsert">
    insert into user(id, name, age) values
    <foreach collection="list" item="item" separator=",">
      (#{item.id},#{item.name},#{item.age})
    </foreach>
  </insert>
public interface UserMapper {
    int batchInsert(List<User> user);
}
public class Test {
    public static void main(String[] args) throws IOException {
        // 创建配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 根据配置文件创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = sqlSessionFactory.openSession();) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User f = new User(8, "f", "23");
            User f1 = new User(9, "f", "23");
            User f2 = new User(10, "f", "23");
            List<User> list = Arrays.asList(f, f1, f2);
            int i = mapper.batchInsert(list);
            System.out.println(i);
        }
    }
}

sql

这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。比如:

<sql id="userColumns"> ${alias}.id,${alias}.name,${alias}.age</sql>
<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

也可以在 include 元素的 refid 属性或内部语句中使用属性值,例如:

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="include_target" value="sometable"/>
  </include>
</select>

参数

下面示例说明了一个非常简单的命名参数映射。鉴于参数类型(parameterType)会被自动设置为 int,这个参数可以随意命名。原始类型或简单数据类型(比如 IntegerString)因为没有其它属性,会用它们的值来作为参数。

  <select id="selectOne" parameterType="int" resultType="hashmap">
    select * from user where `id` = #{id};
  </select>

然而,如果传入一个复杂的对象,行为就会有点不一样了。比如:

  <insert id="insert" parameterType="com.example.mybatisstudy.pojo.User">
    insert into user(id, name, age) values (#{id},#{name},#{age});
  </insert>

如果 User 类型的参数对象传递到了语句中,会查找 idnameage属性,然后将它们的值传入预处理语句的参数中。对传递语句参数来说,这种方式真是干脆利落。

MyBatis 的其它部分一样,几乎总是可以根据参数对象的类型确定 javaType,除非该对象是一个 HashMap。这个时候,你需要显式指定 javaType 来确保正确的类型处理器(TypeHandler)被使用。

#{property,javaType=int,jdbcType=NUMERIC}

要更进一步地自定义类型处理方式,可以指定一个特殊的类型处理器类(或别名),比如:

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

对于数值类型,还可以设置 numericScale 指定小数点后保留的位数。

#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
  • 字符串替换

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:

  <select id="selectOne" parameterType="int" resultType="hashmap">
    select * from user where `id` = #{id} order by ${columnName};
  </select>

SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。

public interface UserMapper {
    @Select("select * from user where  ${columnName} = #{value}")
    User select(@Param("columnName") String columnName, @Param("value") String value);
}

其中 ${column} 会被直接替换,而 #{value} 会使用 ? 预处理。 这样,就能完成同样的任务:

User user = mapper.select("name","张三");

注:这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。

结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

  <select id="selectOne" parameterType="int" resultType="map">
    select * from user where `id` = #{id};
  </select>

示例代码如下:

public interface UserMapper {
    Map selectOne(Integer id);
}
public class Test {
    public static void main(String[] args) throws IOException {
        // 省略代码... ...
        try(SqlSession sqlSession = sqlSessionFactory.openSession();) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            Map map = mapper.selectOne(1);
        }
    }
}

上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBeanPOJOPlain Old Java Objects,普通老式 Java 对象)作为领域模型。

看看下面这个 JavaBean

public class User {
    private Integer id;
    private String name;
    private String age;
    // getter and setter
}

这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单。

  <select id="selectOne" parameterType="int" resultType="com.example.mybatisstudy.pojo.User">
    select * from user where `id` = #{id};
  </select>

前面讲解的类型别名是你的好帮手。使用它们,你就可以不用输入类的全限定名了。

如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。比如:

  <select id="selectOne" parameterType="int" resultType="com.example.mybatisstudy.pojo.User">
    select id as "id", name as "name", age as "age" from user where `id` = #{id};
  </select>

我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。

 <resultMap id="userResultMap" type="User">
   <id property="id" column="id" />
   <result property="name" column="name"/>
   <result property="age" column="age"/>
 </resultMap>
 
 <select id="selectOne" parameterType="int" resultMap="userResultMap">
   select * from user where `id` = #{id};
 </select>

在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。

resultMap 元素有很多子元素和一个值得深入探讨的结构。 下面是resultMap 元素的概念视图。

在这里插入图片描述

  • id & result
<id property="id" column="id" />
<result property="name" column="name"/>

这些元素是结果映射的基础。<id><result> 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。

这两者之间的唯一不同是,<id> 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

两个元素都有一些属性:

在这里插入图片描述

为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。

在这里插入图片描述

  • 构造方法

通过修改对象属性的方式,可以满足大多数的数据传输对象(Data Transfer Object, DTO)以及绝大部分领域模型的要求。 构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBean 属性来完成注入,但有一些人更青睐于通过构造方法进行注入。 <constructor> 元素就是为此而生的。

看看下面这个构造方法:

public class User {
    private Integer id;
    private String name;
    private String age;

    public User(Integer id, String name, String age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    //gettter and setter
} 

为了将结果注入构造方法,MyBatis 需要通过某种方式定位相应的构造方法。

  <resultMap id="userResultMap" type="User">
    <constructor>
      <idArg name="id" column="id"></idArg>
      <arg name="name" column="name"></arg>
      <arg name="age" column="age"></arg>
    </constructor>
  </resultMap>
  
  <select id="selectOne" parameterType="int" resultMap="userResultMap">
    select id,name,age from user where `id` = #{id};
  </select>

剩余的属性和规则,如图。

在这里插入图片描述

  • 关联

<association> 元素是 MyBatis 中用于处理一对一关联关系的元素之一,通常用于在查询结果中映射复杂对象结构。当数据库表之间存在一对一关联关系时,您可以使用 <association> 元素将这些关联关系映射到 Java 对象的属性中。

语法

<association property="userClass" javaType="UserClass"  select="">

(1)property: 指定父对象中保存子对象集合的属性名。

(2)javaType: 指定子对象的类型。

示例代码如下:

public class User {
    private Integer id;
    private String name;
    private String age;
    private UserClass userClass;
    //getter and setter
}
public class UserClass {
    private Integer classId;
    private String className;
    //getter and setter  
}

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.example.mybatisstudy.dao.UserMapper">

    <resultMap id="userResultMap" type="com.example.mybatisstudy.pojo.User">
        <id column="id" property="id" javaType="int"></id>
        <result column="name" property="name" javaType="string"></result>
        <result column="age" property="age" javaType="string"></result>
        <association property="userClass" javaType="UserClass">
            <id column="class_id" property="classId" javaType="int"></id>
            <result column="class_name" property="className" javaType="string"></result>
        </association>
    </resultMap>

    <select id="selectOne" parameterType="int" resultMap="userResultMap">
        select u.id,u.name,u.age,uc.class_id,uc.class_name
        from user u,user_class uc
        where u.id = uc.user_id and u.`id` = #{id};
    </select>

</mapper>

最终打印结果如下

在这里插入图片描述

你也可以将<association> 元素分离出来,使用resultMapid属性关联,示例:

<resultMap id="userResultMap" type="User">
  <id property="id" column="id" />
  <result property="name" column="name"/>
  <association property="userClass" column="user_id" javaType="UserClass" resultMap="userClassResult"/>
</resultMap>

<resultMap id="userClassResult" type="UserClass">
   <id column="class_id" property="classId" javaType="int"></id>
   <result column="class_name" property="className" javaType="string"></result>
</resultMap>

一对一关联关系也可以通过建立一个公用实体类进行映射,就不需要使用<resultMap>标签,返回实体对象即可。

public class UserPO {
    private Integer id;
    private String name;
    private String age;
    private String classId;
    private String className;
    // getter and setter
}
  <select id="selectClass" parameterType="int" resultType="UserPO">
    select 
    user.id as "id", 
	user.name as "name", 
	user.age as "age",
	class.classId as "classId", 
	class.className as "className"
	from user,class where user.id = class.user_id and `id` = #{id};
  </select>
  • 集合

MyBatis 中,<collection> 元素用于处理一对多关联映射,即一个对象包含多个子对象的情况。

语法

<collection property="collectionProperty" ofType="ChildObject" select="statementId"/>

(1)property: 指定父对象中保存子对象集合的属性名。
(2)ofType: 指定子对象的类型。

比如,查询一个班级多个学生,下面是 <collection> 元素的基本用法和示例:

public class User {
    private Integer id;
    private String name;
    private String age;
    private Integer classId;
    //getter and setter
}
public class UserClass {
    private Integer classId;
    private String className;
    private List<User> users;
    //getter and setter  
}

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.example.mybatisstudy.dao.UserMapper">
    <resultMap id="userClassResultMap" type="com.example.mybatisstudy.pojo.UserClass">
        <id column="class_id" property="classId" javaType="int"></id>
        <result column="class_name" property="className" javaType="string"></result>
        <collection property="users" ofType="User">
            <id column="id" property="id" javaType="int"></id>
            <result column="age" property="age" javaType="string"></result>
            <result column="name" property="name" javaType="string"></result>
        </collection>
    </resultMap>
    <select id="selectUserClass" parameterType="int" resultMap="userClassResultMap" >
        select uc.class_id,class_name,id,age,name from user_class uc left join  user u on uc.class_id=u.class_id where uc.class_id = #{id}
    </select>

</mapper>

最终打印结果如下

在这里插入图片描述
不需要配置<collection> 元素里面的属性,他会自己映射对应的名称。resultMap属性再ofType属性中好像并不生效。

<?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.example.mybatisstudy.dao.UserMapper">
    <resultMap id="userClassResultMap" type="com.example.mybatisstudy.pojo.UserClass">
        <id column="class_id" property="classId" javaType="int"></id>
        <result column="class_name" property="className" javaType="string"></result>
        <collection property="users" ofType="User"></collection>
    </resultMap>
    <select id="selectUserClass" parameterType="int" resultMap="userClassResultMap" >
        select uc.class_id,class_name,id,age,name from user_class uc left join  user u on uc.class_id=u.class_id where uc.class_id = #{id}
    </select>
</mapper>
  • 鉴别器

有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。

<resultMap id="userClassResultMap" type="com.example.mybatisstudy.pojo.User">
    <id column="id" property="id" javaType="int"></id>
    <result column="name" property="name" javaType="string"></result>
    <discriminator column="type" javaType="int">
        <case value="1">
            <result property="field1" column="field"></result>
        </case>
        <case value="2">
            <result property="field2" column="field"></result>
            <result property="field3" column="field"></result>
        </case>
        <case value="3">
            <result property="field4" column="field"></result>
        </case>
    </discriminator>
</resultMap>

你也可以将<case>标签里面的属性分离,使用resultMapid属性关联,示例:

<resultMap id="userResultMap" type="com.example.mybatisstudy.pojo.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <!-- 使用 discriminator 根据 type 列的值来动态选择映射规则 -->
    <discriminator column="type" javaType="int">
        <case value="1" resultMap="res1"/>
        <case value="2" resultMap="res2"/>
        <case value="3" resultMap="res3"/>
    </discriminator>
</resultMap>

<resultMap id="res1" type="com.example.mybatisstudy.pojo.Student">
    <result property="field1" column="field"></result>
</resultMap>

<resultMap id="res2" type="com.example.mybatisstudy.pojo.Teacher">
    <result property="field2" column="field"></result>
    <result property="field3" column="field"></result>
</resultMap>

<resultMap id="res3" type="com.example.mybatisstudy.pojo.Staff">
    <result property="field4" column="field"></result>
</resultMap>

自动映射

MyBatis 中,如果您使用了 <resultMap> 进行结果映射配置,同时没有手动指定每个列应该映射到目标对象的哪个属性上,MyBatis 会尝试根据列名自动映射到目标对象的属性上。这种自动映射的方式称为自动映射(Auto-mapping)。

<resultMap id="userResultMap" type="com.example.mybatisstudy.pojo.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <!-- 其他属性会自动映射 -->
</resultMap>

可以通过在结果映射上设置 autoMapping 属性来为指定的结果映射设置启用/禁用自动映射。

<resultMap id="userResultMap" type="com.example.mybatisstudy.pojo.User" autoMapping="false">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <!-- 其他属性会自动映射 -->
</resultMap>

缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

(1)一级缓存(Local Cache):

默认情况下,MyBatis 开启了一级缓存,它是基于 SqlSession 的缓存,同一个 SqlSession 内执行的相同查询会共享一级缓存。

@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class Test {
    public static void main(String[] args) throws IOException {
        // 创建配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 根据配置文件创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = sqlSessionFactory.openSession();) {
            ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
            List<Product> list = mapper.list();
            List<Product> list2 = mapper.list();
        }
    }
}

上述代码调用两次查询方法,但在执行过程中只会进行一次查询,第二次会直接从缓存中获取,执行结果如图

在这里插入图片描述

一级缓存可以通过 sqlSession.clearCache() 手动清空,或者在执行更新操作时会自动刷新缓存。

(2)二级缓存(Global Cache):

二级缓存是基于 namespace 级别的缓存,可以跨 SqlSession 共享缓存数据。

若要使用二级缓存,需要在映射文件中开启 <cache> 标签。

<?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.example.demo.mapper.ProductMapper">
    <cache></cache>
    <resultMap id="BaseResultMap" type="com.example.demo.domain.Product">
        <id property="id" column="id" jdbcType="INTEGER"/>
        <result property="productName" column="product_name" jdbcType="VARCHAR"/>
        <result property="number" column="number" jdbcType="INTEGER"/>
    </resultMap>

    <sql id="Base_Column_List">
        id
        ,product_name,number
    </sql>
    <select id="list" resultMap="BaseResultMap">
        select *
        from product
    </select>
</mapper>

SqlSession对象调用close方法关闭,⼀级缓存中的数据才会被写⼊到⼆级缓存。

@RestController
public class MyController {
    @Autowired
    ProductMapper productMapper;

    @GetMapping("/test")
    public void test() {
        productMapper.list();
    }

}

请求两次后,执行结果如图

在这里插入图片描述

我们可以通过ibatis提供的Cache接口,自定义缓存策略(以 Redis 为例):

<?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.example.demo.mapper.ProductMapper">
	<!--指定缓存类-->
    <cache type="com.example.demo.cache.MybatisCache"></cache>
    <resultMap id="BaseResultMap" type="com.example.demo.domain.Product">
        <id property="id" column="id" jdbcType="INTEGER"/>
        <result property="productName" column="product_name" jdbcType="VARCHAR"/>
        <result property="number" column="number" jdbcType="INTEGER"/>
    </resultMap>

    <sql id="Base_Column_List">
        id
        ,product_name,number
    </sql>
    <select id="list" resultMap="BaseResultMap">
        select *
        from product
    </select>
</mapper>

示例代码如下:

public class MybatisCache implements Cache {

    private static RedisTemplate<String,Object> redisTemplate;

    private final String id;

    /**
     * The {@code ReadWriteLock}.
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    public MybatisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }
    //初始化时通过配置类将RedisTemplate给过来
    public static void setTemplate(RedisTemplate<String, Object> template) {
        MybatisCache.redisTemplate = template;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        try{
            System.out.println("插入数据");
            if(null!=value){
                redisTemplate.opsForValue().set(key.toString(),value,60, TimeUnit.SECONDS);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public Object getObject(Object key) {
        try{
            System.out.println("获取数据");
            if(null!=key){
                return redisTemplate.opsForValue().get(key.toString());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        System.out.println("删除数据");
        return redisTemplate.delete((String) key);
    }

    @Override
    public void clear() {
        //由于template中没封装清除操作,只能通过connection来执行
        redisTemplate.execute((RedisCallback<Void>) connection -> {
            //通过connection对象执行清空操作
            connection.flushDb();
            return null;
        });
    }

    @Override
    public int getSize() {
        //这里也是使用connection对象来获取当前的Key数量
        return redisTemplate.execute(RedisServerCommands::dbSize).intValue();	//这里导的类是redis下的
    }
}
@Component
public class RedisCacheTransfer {
    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        MybatisCache.setTemplate(redisTemplate);
    }
}

请求两次后,执行结果如图
在这里插入图片描述

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 truefalse。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false

提示 :二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=trueinsert/delete/update 语句时,缓存会获得更新。

  • cache-ref

对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。

<cache-ref namespace="com.example.mybatisstudy.dao.UserClassMapper"/>

设计初期的 MyBatis 是一个 XML 驱动的框架。而在 MyBatis 3 中,我们提供了其它的配置方式。注解提供了一种简单且低成本的方式来实现简单的映射语句。不幸的是,Java 注解的表达能力和灵活性十分有限。

@Results(id = "userResult", value = {
  @Result(property = "id", column = "id", id = true),
  @Result(property = "name", column = "name"),
  @Result(property = "age", column = "age")
})
@Select("select * from user where id = #{id}")
User getUserById(Integer id);

@Results(id = "userClassResults")
@ConstructorArgs({
  @Arg(column = "class_id", javaType = Integer.class, id = true),
  @Arg(column = "class_name", javaType = String.class)
})
@Select("select * from user_class where user_id = #{id}")
UserClass getUserClass(Integer id);

还有更多注解,如图所示
在这里插入图片描述

动态SQL

动态 SQLMyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

MyBatis 提供了以下几种方式来实现动态 SQL

  1. 使用 <if> 元素
    <select id="selectByUser" parameterType="User" resultType="User">
        select * from user
        <where>
            <if test="name != null and name != ''">
                and name = #{name}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
        </where>
    </select>

你可以使用or或者and代替||&&进行逻辑运算,一般情况下建议强制一个字段传值,防止查询全部数据。

你也可以不使用<where>标签,改用1=1,但是不建议你这样做。

    <select id="selectByUser" parameterType="User" resultType="User">
        select * from user where 1=1
        <if test="name != null and name != ''">
            and name = #{name}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
    </select>

注:where语句要么使用 <where>标签,要么后面有条件,比如:where 1=1,否则会出现语句错误。

  1. 使用 <choose><when><otherwise> 元素

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 <choose> 元素,它有点像 Java 中的 switch 语句。

    <select id="selectByUser" parameterType="User" resultType="User">
        select * from user
        <where>
            <choose>
                <when test="name != null and name != ''">
                    and name = #{name}
                </when>
                <when test="age != null and age != ''">
                    and age = #{age}
                </when>
                <otherwise>
                    and name ='a' and age=12
                </otherwise>
            </choose>
        </where>
    </select>
  1. 使用 <trim> 元素

可以去除或者包裹 SQL 片段的开头或结尾,常用于处理 WHEREANDOR 等关键字。

    <select id="selectByUser" parameterType="User" resultType="User">
        select * from user
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name != null and name != ''">
                and name = #{name}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
        </trim>
    </select>

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

    <update id="update" parameterType="com.example.mybatisstudy.pojo.User">
        UPDATE `spring_data`.`user`
        <set>
            <if test="name != null">`name` = #{name},</if>
            <if test="age != null">`age` = #{age},</if>
        </set>
        WHERE `id` = #{id};
    </update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

或者,你可以通过使用trim元素来达到同样的效果:

    <update id="update" parameterType="com.example.mybatisstudy.pojo.User">
        UPDATE `spring_data`.`user`
        <trim prefix="set" suffixOverrides=",">
            <if test="name != null">`name` = #{name},</if>
            <if test="age != null">`age` = #{age},</if>
        </trim>
        WHERE `id` = #{id};
    </update>
  1. 使用 <foreach> 元素

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

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

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

script

要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:

    @Update({"<script>",
            "UPDATE user",
            "<set>",
            "<if test='name != null'>`name` = #{name},</if>",
            "<if test='age != null'>`age` = #{age},</if>",
            "</set>",
            "WHERE `id` = #{id};",
            "</script>"})
    int update(User user);

动态查询也可以用,如下:

    @Select({"<script>",
            "select * from user",
            "<where>",
            "<if test='name != null'> and name = #{name} </if>",
            "<if test='age != null'> and age = #{age} </if>",
            "</where>",
            "</script>"})
    List<User> selectByUser(User user);

SQL 语句构建器

Java 程序员面对的最痛苦的事情之一就是在 Java 代码中嵌入 SQL 语句。MyBatis 3 提供了方便的工具类来帮助解决此问题。借助 SQL 类,我们只需要简单地创建一个实例,并调用它的方法即可生成 SQL 语句。

public class UserSQL {

    // 匿名内部类风格
    public String userSql(){
        return new SQL() {{
            SELECT("id,name,age");
            FROM("user");
            WHERE("id =#{id}");
        }}.toString();
    }
    
    // Builder / Fluent 风格
    public String userSql2() {
        String sql = new SQL()
                .SELECT("*")
                .FROM("user")
                .WHERE("id =#{id}")
                .toString();
        return sql;
    }

    // 动态条件
    public String selectPersonLike(User user){
        return new SQL(){{
            SELECT("id,name,age");
            FROM("user");
            if (user.getId() != null) {
                WHERE("id =#{id}");
            }
            if (user.getName() != null) {
                WHERE("name =#{name}");
            }
            if (user.getAge() != null) {
                WHERE("age =#{age}");
            }

        }}.toString();
    }
}
public interface UserMapper {
    @SelectProvider(type = UserSQL.class, method = "selectPersonLike")
    List<User> selectByUser(User user);
}
public class Test {
    public static void main(String[] args) throws IOException {
        // 创建配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 根据配置文件创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = sqlSessionFactory.openSession();) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User user = new User();
            user.setAge("12");
            user.setId(1);
            List<User> i = mapper.selectByUser(user);
            System.out.println(i.toString());
        }
    }
}

更多用法如图
在这里插入图片描述

日志

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j(3.5.9起废弃)
  • JDK logging

如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志工具,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择别的日志工具。

<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  </settings>
</configuration>

logImpl 可选的值有:SLF4JLOG4JLOG4J2JDK_LOGGINGCOMMONS_LOGGINGSTDOUT_LOGGINGNO_LOGGING,或者是实现了接口 org.apache.ibatis.logging.Log 的,且构造方法是以字符串为参数的类的完全限定名。

先导入pom依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

在资源文件夹resources下创建log4j的配置文件 log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#定义日志输出的格式模式
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/main.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
#定义日志输出的格式模式
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

输出日志如下:
在这里插入图片描述

其他配置不做详细讲解,有兴趣自行了解。

分页插件

再讲解分页插件之前,先来看一下原始的分页如编写。

public interface UserMapper {
    List<User> selectByUser(Map<String,Object> map);
}
public class Test {
    public static void main(String[] args) throws IOException {
        // 创建配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 根据配置文件创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = sqlSessionFactory.openSession();) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            Map<String,Object> map = new HashMap<>();
            map.put("currentPage",0);
            map.put("pageSize",3);
            List<User> i = mapper.selectByUser(map);
            System.out.println(i.toString());
        }
    }
}

Mapper文件如下:

    <select id="selectByUser" parameterType="map" resultType="User">
        select * from user limit #{currentPage},#{pageSize}
    </select>

原始写法需要手动编写分页语句,我们可以使用插件(pagehelper)来完成分页操作(原理:自动添加limit,并进行优化)。

先导入依赖

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>6.1.0</version>
        </dependency>

mybatis-config.xml配置插件

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

使用如下:

public class Test {
    public static void main(String[] args) throws IOException {
        // 创建配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 根据配置文件创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = sqlSessionFactory.openSession();) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            Page<Object> objects = PageHelper.startPage(1, 3);
            List<User> list = mapper.selectByUser();
            PageInfo<User> pageInfo = new PageInfo<>(list);
            System.out.println("数据:"+pageInfo.getList().toString());
            System.out.println("上一页:"+pageInfo.getPrePage());
            System.out.println("下一页:"+pageInfo.getNextPage());
            System.out.println("总数:"+pageInfo.getTotal());
            System.out.println("总页数:"+pageInfo.getPages());

        }
    }
}

如图所示

在这里插入图片描述

逆向工程

如果创建的表很多,总不可能一个个手动去添加,这样太浪费时间,在 MyBatis 中,可以使用 MyBatis Generator 工具自动生成对应的 Java 实体类、Mapper 接口和 XML 映射文件的过程。

先创建generatorConfig.xml生成的一些配置文件。

<?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">
<!--参考http://mybatis.org/generator/configreference/xmlconfig.html-->

<generatorConfiguration>

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

        <!--数据库连接驱动类,URL,用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/spring_data?serverTimezone=UTC"
                        userId="root"
                        password="123456">
            <property name="nullCatalogMeansCurrent" value="true" />
        </jdbcConnection>

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- 生成(实体)模型的包名和位置-->
        <javaModelGenerator targetPackage="com.example.mybatisstudy.pojo" targetProject="src\main\java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--生成XML映射文件和位置-->
        <sqlMapGenerator targetPackage="main.resources.mappers" targetProject="src">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成DAO接口的包名和位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mybatisstudy.dao"
                             targetProject="src\main\java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!--生成对应表配置-->
        <table schema="root" tableName="user"
               enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true"
               enableSelectByExample="true" selectByExampleQueryId="true"></table>
        <table schema="root" tableName="user_class"
               enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true"
               enableSelectByExample="true" selectByExampleQueryId="true"></table>
    </context>
</generatorConfiguration>

pom文件中添加依赖和插件

    <dependencies>
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.7</version>
        </dependency>
    </dependencies>
    <build>
     <plugins>
         <!--mybatis generator自动生成代码插件-->
         <!--Idea中使用此插件方法生成代码的方法:配置完generatorConfig.xml后,在idea右侧Maven面板-本项目-Plugins-刷新-找到mybatis-generate-点击右键Run Maven Build即可生成代码-->
         <plugin>
             <groupId>org.mybatis.generator</groupId>
             <artifactId>mybatis-generator-maven-plugin</artifactId>
             <version>1.3.7</version>
             <configuration>
                 <configurationFile>src\main\resources\generatorConfig.xml</configurationFile>
                 <overwrite>true</overwrite>
                 <verbose>true</verbose>
             </configuration>
             <dependencies>
                 <dependency>
                     <groupId>mysql</groupId>
                     <artifactId>mysql-connector-java</artifactId>
                     <version>8.0.22</version>
                 </dependency>
             </dependencies>
         </plugin>
     </plugins>
    </build>

idea为例,找到右侧,maven面板,点击执行,如图所示

在这里插入图片描述
生成完毕后,就可以再项目结构中找到生成的代码,里面包含自动生成的语句等。

在这里插入图片描述

SpringBoot整合Mybatis

如果是SpringBoot项目,所需依赖如下:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

application.properties配置文件(mybatis-config.xml中的内容都可以在里面进行配置),如下

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_data?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
mybatis.mapper-locations=classpath:/mappers/*.xml
#开启sql日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.log4j.Log4jImpl

然后再Mapper类中加上@Mapper注解。

@Mapper
public interface UserMapper {
    User selectByPrimaryKey(Integer id);
]

编写控制层代码

@Controller
public class MyController {
    @Autowired
    UserMapper userMapper;

    @GetMapping(value = "/get")
    public ResponseEntity<User> get(HttpServletResponse response) throws Exception {
        User user = userMapper.selectByPrimaryKey(1);
        return ResponseEntity.ok().body(user);
    }
}

启动项目
在这里插入图片描述

访问接口,如图所示

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值