MyBatis

半自动 ORM Object Relation Mapping 持久层框架 MyBatis

Green Trees Under Blue Sky
Green Trees Under Blue Sky

快速上手

JDBC 存在硬编码和耦合度高的问题;全自动框架 Hibernate 不易作特殊优化,且反射操作过多。

maven 配置

<!-- pom.xml -->
<dependencies>
    <!-- Mybatis核心 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- MySQL驱动 -->
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
    <!-- log4j日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

核心配置文件

src/main/resources 目录下的核心配置文件 mybatis-config.xml 用于连接数据库环境以及 MyBatis 全局配置信息。整合 Spring 之后,此配置文件可省略。

<?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>
    <!-- 核心配置文件中的标签必须按固定的顺序书写 -->
    <!-- The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)". -->
    <!-- 引入properties文件 -->
    <properties resource="jdbc.properties" />
    <!-- 设置类型别名 => 类型别名不区分大小写 -->
    <typeAliases>
        <!-- typeAlias 设置某个类型的别名,不设置 alias 则默认以不区分大小写的类名表示 -->
        <!-- <typeAlias type="com.zszy.mybatis.pojo.User" alias="User"></typeAlias> -->
        <!-- 以包为单位将包下所有的类型设置默认的类型别名,即类名且不区分大小写 -->
        <package name="com.zszy.mybatis.pojo"/>
    </typeAliases>
    <!--设置连接数据库的环境-->
    <!--
    environments => 设置多个连接数据库的环境
    属性 default => 设置默认使用的环境的id
    -->
    <environments default="development">
        <!--
        environment => 设置具体的连接数据库的环境信息
        属性id => 设置环境的唯一标识;可通过environments标签中的default设置某一个环境的id,表示默认使用的环境
        -->
        <environment id="development">
            <!--
            transactionManager => 设置事务管理方式
            属性 type  => 设置事务管理方式 => type="JDBC|MANAGED"
	            type="JDBC" => 设置当前环境的事务管理都必须手动处理
	            type="MANAGED" => 设置事务被管理,例如spring中的AOP
            -->
            <transactionManager type="JDBC"/>
            <!--
            dataSource => 设置数据源
            属性 type => 设置数据源的类型 => type="POOLED|UNPOOLED|JNDI"
	            type="POOLED" => 使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建
	            type="UNPOOLED" => 不使用数据库连接池;每次使用连接都需要重新创建
	            type="JNDI" => 调用上下文中的数据源
            -->
            <dataSource type="POOLED">
                <!-- alt + shift + 上下 -->
                <property name="driver" value="${jdbc.driver}"/>
                <!-- <property name="url" value="jdbc:mysql://localhost:3306/MyBatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=UTF-8"/> -->
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 引入映射文件 -->
    <mappers>
        <!-- <mapper resource="mappers/UserMapper.xml"/> -->
        <!-- 以包为单位映射配置文件 => 注意在 resources 中创建包不能以.分割,而是应该用/ -->
        <!-- 此包下所有映射文件都会引入核心配置文件中 => mapper即是空偶所在的包要与映射文件所在的包一致;mapper接口要与映射文件的名字一致 -->
        <package name="com.zszy.mybatis.mapper"/>
    </mappers>
</configuration>

mapper 接口

MyBatis 中的 mapper 接口相当于 dao,区别在于 mapper 仅是接口,无需提供实现类。

public interface <functionOperater> {
    /**
     * mybatis 面向接口编程的两个一致
     * => 映射文件的命名空间 namespace 与接口的全类名一致;
     * => 映射文件中 SQL 语句 id 与 mapper 接口中方法名一致
     */
    /**
     * xxx操作
     */
    <returnValue> <functionOperater>();
}

映射文件

  • ORM Object Relationship Mapping 对象关系映射
    • 对象即实体类对象,关系即为关系型数据库,映射是二者之间的对应关系
    • 类对应数据库中的表,属性对应数据库中的字段和列,对象对应数据库中的记录和行
  • 映射文件的命名规则 => 表所对应实体类的类名 + Mapper.xml
  • 一个映射文件对应一个实体类,对应一张表的操作
  • MyBatis映射文件用于编写SQL,访问以及操作表中的数据
  • MyBatis映射文件存放的位置是src/main/resources/mappers目录下
  • MyBatis中可以面向接口操作数据,要保证两个一致
    • mapper接口的全类名和映射文件的命名空间 namespace 保持一致
    • mapper接口中方法的方法名和映射文件中编写 SQL 的标签的 id 属性保持一致
<!-- src/main/resources/com.../mappers/XxxMapper.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="">
    <!-- returnValue operatorMethod(); -->
    <!-- <sql operater>sql</sql operater> -->
</mapper>

查询的标签 select 必须设置属性 resultType 或 resultMap,用于设置实体类和数据库表的映射关系。前者表示自动映射,用于属性名和表中字段名一致的情况;后者为自定义映射,用于一对多或多对一或字段名和属性名不一致的情况。

当查询的数据为多条时,不能使用实体类作返回值,只能使用集合,否则会抛出异常 TooManyResultsException;若查询的数据只有一条,则可使用实体类或集合作为返回值。

<!-- int insertUser(); -->
<insert id="insertUser"> insert into t_user values(null,'admin','123456',23,'男','12345@qq.com') </insert>
<!-- int deleteUser(); -->
<delete id="deleteUser"> delete from t_user where id = 6 </delete>
<!-- int updateUser(); -->
<update id="updateUser"> update t_user set username = 'zs' where id = 5 </update>
<!-- User getUserById(); -->  
<select id="getUserById" resultType="com.xxx.mybatis.bean.User"> select * from t_user where id = 2 </select>
<!-- List<User> getUserList(); -->
<select id="getUserList" resultType="com.xxx.mybatis.bean.User"> select * from t_user </select>

junit 功能测试

  • SqlSession 是 Java 程序和数据库之间的会话;HttpSession 是 Java 程序和浏览器之间的会话。
public interface SqlSession extends Closeable { ...增删改查事务 }
  • SqlSessionFactory 是生产 SqlSession 的工厂。
public interface SqlSessionFactory {
    SqlSession openSession(...); // SqlSessionFactory.openSession(true);
    ...
    Configuration getConfiguration();
}
  • SqlSession 默认不自动提交事务,需手动提交。
// 开启自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
public class MyBatistest {
    @Test
    public void testMyBatis() throws IOException {
        // 加载核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        // 获取 SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 获取sqlSessionFactoryBuilder
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        // 获取 mybatis 操作数据库的会话对象 sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession(true); // autoCommit
        // 获取 mapper 接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 测试功能
        <returnValue> rv = mapper.<operatorMethod>();
        // 提交事务
        // sqlSession.commit();
        System.out.println("result => " +rv);
    }
}

log4j 日志功能

Slf4j 类似于 Commons Logging,是一个日志接口,而 Logback 类似于 Log4j,是一个日志的实现。

  • Maven 依赖
<!-- log4j 日志 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  • log4j 配置文件

log4j 的配置文件名为 log4j.xml,存放的位置是 src/main/resources 目录;日志级别包括 FATAL、ERROR、WARN、INFO、DEBUG。

<!-- log4j.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>

MyBatis 获取参数值的方式

MyBatis 获取参数的两种方式为 ${}#{}${} 本质是字符串拼接,#{} 本质是占位符赋值。

使用 ${} 对字符串类型或日期类型拼接时,需要手动加单引号;使用 #{} 拼接字符串类型或日期类型时,会自动添加单引号。

建议分成两种情况进行处理,一种是实体类型、一种是 @Param。

单字面量类型参数

若 mapper 接口中的方法参数为单个的字面量类型,可使用 ${}#{} 获取参数值,注意前者需手动加单引号。

public interface ParameterMapper { ...
    // 根据用户名查询用户信息
    User getUserByUsername(String username);
}
<!-- User getUserByUsername(String username); -->
<select id="getUserByUsername" resultType="User">
    <!-- select * from t_user where username = #{username} -->
    select * from t_user where username = '${username}'
</select>
<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User"> select * from t_user where username = '${username}' </select>
<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User"> select * from t_user where username = #{username} </select>

多字面量类型参数

若 mapper 接口中的方法参数为多个字面量类型,MyBatis 会自动将这些参数在兼容处理后放入 map 集合。以 arg[0-n]param[1-n] 键,以参数为值。

通过 ${}#{} 访问集合的键就可获取相应的值。注意 ${} 仍需手动加单引号;且 arg 是从下标 0 开始,param 是从下标 1 开始。

public interface ParameterMapper { ...
    // 验证登录
    User checkLogin(String username,String password);
}
<select id="checkLogin" resultType="User">
    <!-- mybatis底层检测多个参数使用 arg 或者 param -->
    <!-- select * from t_user where username = #{arg0} and password = #{arg1} -->
    <!-- select * from t_user where username = #{param1} and password = #{param2} -->
    select * from t_user where username = '${param1}' and password = '${arg1}'
</select>
<!--User checkLogin(String username,String password);-->
<select id="checkLogin" resultType="User"> select * from t_user where username = #{arg0} and password = #{arg1} </select>
<!--User checkLogin(String username,String password);-->
<select id="checkLogin" resultType="User"> select * from t_user where username = '${param1}' and password = '${param2}' </select>

map 集合类型参数

若 mapper 接口中的方法需要的参数为多个时,可手动创建 map 集合并将数据放入,通过 ${}#{} 访问 map 集合的键就可以获取相对应的值,注意 ${} 需要手动加单引号。

public interface ParameterMapper { ...
    // 验证登录 => 参数为map
    User checkLoginByMap(Map<String,Object> map);
}
<!--User checkLoginByMap(Map<String,Object> map);-->
<select id="checkLoginByMap" resultType="User"> select * from t_user where username = #{username} and password = #{password} </select>
@Test
public void checkLoginByMap() {
	SqlSession sqlSession = SqlSessionUtils.getSqlSession();
	ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
	Map<String,Object> map = new HashMap<>();
	map.put("usermane","admin");
	map.put("password","123456");
	User user = mapper.checkLoginByMap(map);
	System.out.println(user);
}

实体类类型参数

若 mapper 接口中的方法参数为实体类对象时,可使用 ${}#{},通过访问实体类对象中的属性名获取属性值,注意 ${} 需要手动加单引号。

public interface ParameterMapper { ...
    // 添加用户信息
    Integer insertUser(User user);
}
<!-- int insertUser(User user); -->
<!-- 属性和成员变量关系不大,不过不否认是成员变量,主要看得是 set 和 get 方法!!!!! -->
<select id="insertUser"> insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email}) </select>
@Test
public void insertUser() {
	SqlSession sqlSession = SqlSessionUtils.getSqlSession();
	ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
	User user = new User(null,"gz","123456",20,"男","gz@mail.com");
	mapper.insertUser(user);
}

@Param 标识参数

使用 @Param 注解标识的 mapper 接口中的方法参数会被加入 map 集合,可通过 ${}#{} 访问此集合的键以获取对应值,注意 ${} 需要手动加单引号。

public interface ParameterMapper { ...
    // 验证登录 => @Param
    User checkLoginByParam(@Param("username") String username, @Param("password") String password);
}
<!-- User checkLoginByParam(@Param("username") String username, @Param("password") String password); -->
<select id="checkLoginByParam" resultType="User"> select * from t_user where username = '${username}' and password = '${password}' </select>
@Test
public void testCheckLoginByParam(){
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
    User user = mapper.checkLoginByParam("hz", "123321");
    System.out.println(user);
}

@Param 注解源码

convertArgsToSqlCommandParam => getNamedParams => this.names = Collections.unmodifiableSortedMap(map);

param1 开始是因为 GENERIC_NAME_PREFIX + 1。

CRUD

MyBatis 的查询

若查询出的数据只有一条,可通过实体类对象、List 集合以及 Map 集合接收。如果查询出的数据有多条,那么一定不能用实体类对象进行接收,否则会抛出异常 TooManyResultsException,应该通过实体类类型的 List 集合、Map 类型的 List 集合或在 mapper 接口的方法上添加 @MapKey 注解表示指定字段为键进行接收。

public interface SelectMapper {
    // 根据id查询用户信息
    List<User> getUserById(@Param("id") Integer id);
    // 查询所有用户信息
    List<User> getAllUser();
    // 查询用户表总记录数
    Integer getCount();
    // 根据id查询用户信息为一个map集合
    Map<String, Object> getuserByIdToMap(@Param("id") Integer id);
    // 查询所有用户信息为 map 集合
    List<Map<String, Object>> getAllUserToMap();
    // 查询所有用户信息为 map 集合 => 注解
    @MapKey("id")
    Map<String, Object> getAllUserToMapAnother();
}
<mapper namespace="com.zszy.mybatis.mapper.SelectMapper">
    <!-- User getUserById(@Param("id") Integer id); -->
    <select id="getUserById" resultType="User">
        select * from t_user where id = #{id}
    </select>
    <!-- List<User> getAllUser(); -->
    <select id="getAllUser" resultType="User">
        select * from t_user
    </select>
    <!-- Integer getCount(); -->
    <select id="getCount" resultType="java.lang.Integer">
        <!-- 注意如果count(字段)对应null则不会放入查询结果;count(*) 和 count(1) 结果是一样的 -->
        select count(*) from t_user
    </select>
    <!-- Map<String, Object> getuserByIdToMap(@Param("id") Integer id); -->
    <select id="getuserByIdToMap" resultType="java.util.Map">
        select * from t_user where id = #{id}
    </select>
    <!-- List<Map<String, Object>> getAllUserToMap(); -->
    <select id="getAllUserToMap" resultType="map">
        select * from t_user
    </select>
    <!--
    @MapKey("id")
    Map<String, Object> getAllUserToMapAnother();
    -->
    <select id="getAllUserToMapAnother" resultType="map">
        select * from t_user
    </select>
</mapper>
public class SelectMapperTest {
    /**
     * MyBatis 各种查询功能
     * 1.若查询出的数据只有一条,则可通过实体类对象或者 "集合" 或者 map 接收
     * 2.若查询出数据多条,一定不能通过实体类对象接收,此时会抛出异常 TooManyResultsException,可通过实体类对象的list接收;可通过 map 类型的集合接收;可以在mapper接口的方法上添加@Mapkey注解,此时每条数据转换的map集合作为值,以某个字段作为键,放在同一个map集合中
     */
    @Test
    public void testGetUserById(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        System.out.println(mapper.getUserById(3));
    }
    @Test
    public void testGetAllUser(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        System.out.println(mapper.getAllUser());
    }
    @Test
    public void testGetCount(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        System.out.println(mapper.getCount());
    }
    @Test
    public void testUserByIdToMap(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        System.out.println(mapper.getuserByIdToMap(3));
    }
    @Test
    public void testAllUserToMap(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        System.out.println(mapper.getAllUserToMap());
    }
    @Test
    public void testAllUserToMapAnother(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);
        System.out.println(mapper.getAllUserToMapAnother());
    }
}

特殊 SQL 执行

  • 模糊查询
public interface SQLMapper {
    // 根据用户名模糊查询用户信息
    List<User> getUserByLike(@Param("username") String username);
}
<mapper namespace="com.zszy.mybatis.mapper.SQLMapper">
    <!-- List<User> getUserByLike(@Param("username")String username); -->
    <select id="getUserByLike" resultType="User">
        <!-- select * from t_user where username like '%#{username}%' => org.apache.ibatis.exceptions.PersistenceException -->
        <!-- select * from t_user where username like '%${username}%' -->
        <!-- select * from t_user where username like concat('%',#{username},'%') -->
        select * from t_user where username like "%"#{username}"%"
    </select>
</mapper>
public class SQLMapperTest {
    @Test
    public void testSQLMapper(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
        List<User> list = mapper.getUserByLike("g");
        System.out.println(list);
    }
}
  • 批量删除

只能使用 ${}。如果使用 #{},则解析后的 sql 语句会将传入参数视作一个整体,这个整体不会被成功匹配。

public interface SQLMapper {
    // 批量删除
    int deleteMore(@Param("ids") String ids);
}
<mapper namespace="com.zszy.mybatis.mapper.SQLMapper">
    <!-- int deleteMore(@Param("ids") String ids); -->
    <delete id="deleteMore">
        <!-- 批量删除不能使用 #{} => #{} 会自动加上单引号 -->
        delete from t_user where id in (${ids})
    </delete>
</mapper>
public class SQLMapperTest {
    @Test
    public void testDeleteMore(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
        int i = mapper.deleteMore("1,2,6");
        System.out.println(i);
    }
}
  • 动态设置表名

只能使用 ${},因表名不能加单引号。

public interface SQLMapper {
    // 查询指定表中数据
    List<User> getUserByTableName(@Param("tableName") String tableName);
}
<mapper namespace="com.zszy.mybatis.mapper.SQLMapper">
    <!-- List<User> getUserByTableName(@Param("tableName") String tableName); -->
    <select id="getUserByTableName" resultType="User">
        <!-- 这里要注意表名不能加单引号 => 只能使用 ${} -->
        select * from ${tableName}
    </select>
</mapper>
public class SQLMapperTest {
    @Test
    public void testGetUserByTableName(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
        List<User> list = mapper.getUserByTableName("t_user");
        System.out.println(list);
    }
}
  • 添加功能获取自增的主键

在 mapper.xml 中设置属性 useGeneratedKeyskeyProperty。因为增删改有统一受影响的行数的返回值,因此只能将获取的自增主键放在传输的参数对象中的某个属性。

public interface SQLMapper {
    // 添加用户信息
    void insertUser(User user);
}
<mapper namespace="com.zszy.mybatis.mapper.SQLMapper">
    <!-- void insertUser(User user); -->
    <!-- useGeneratedKeys 设置当前标签中 sql 使用了自增的主键 -->
    <!-- keyProperty 将自增的主键的值赋值给传输到映射文件中参数的某个属性 -->
    <!-- 应用场景 => 一对多、多对多 设置中间表的主键 -->
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})
    </insert>
</mapper>
public class SQLMapperTest {
    @Test
    public void testInsertUser(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);
        User user = new User(null,"hz","123321",24,"男","hz@mail.com");
        mapper.insertUser(user);
        System.out.println(user); // 执行添加操作后id有值
    }
}

结果集|自定义映射 resultMap

简单映射语句中不一定需要显式指定 resultMap,只需使用 resultType 指定能够接收的存储类型即可。但这种语句只是简单地将所有的列映射到存储类型的键上,不能像 JavaBean 这般的领域模型对系统中的各个实体及其之间的关系进行描述。

当领域模型通过 resultType 指定,其底层会在幕后自动创建一个 resultMap,再根据属性名来映射列到 JavaBean 的属性上。注意,若字段名和实体类中属性名不一致,可以设置自定义映射。

public interface EmpMapper {
    // 查询所有数据
    List<Emp> getAllEmp();
}
<!-- 下边两种指定效果一致 -->
<!-- resultType 指定领域模型 -->
<select id="getAllEmp" resultType="Emp">
    select eid as "eid", emp_name as "empName", age as "age", sex as "sex", email as "email" FROM t_emp
</select>
<!-- resultMap 指定领域模型 -->
<resultMap id="empResultMap" type="Emp">
    <!-- id 专门设置主键映射关系 -->
    <id property="eid" column="eid"></id>
    <!-- result 设置普通字段的映射关系 -->
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
</resultMap>
<select id="getAllEmp"  resultMap="empResultMap">
    select * from t_emp
</select>
public class ResultMapTest {
    @Test
    public void testGetAllEmp(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        List<Emp> allEmp = mapper.getAllEmp();
        allEmp.forEach(emp -> System.out.println(emp));
    }
}

核心配置文件中 <setting> 标签的 mapUnderscoreToCamelCase 用于设置查询表数据时,将 _ 类型的字段名自动转换为驼峰。

在提供了结果映射后,自动映射也能工作。在这种情况下,对于每一个结果映射,在 ResultSet 出现的列,如果没有设置手动映射,将被自动映射。在自动映射处理完毕后,再处理手动映射。

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

多对一映射处理

  • 级联属性赋值
public interface EmpMapper {
    // 查询员工以及员工所对应的部门信息
    Emp getEmpAndDept(@Param("eid") Integer eid);
}
<resultMap id="getEmpAndDeptResultMapOne" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
    <result property="dept.did" column="did"></result>
    <result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="getEmpAndDeptResultMapOne">
    select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>
  • association 标签
public interface EmpMapper {
    // 查询员工以及员工所对应的部门信息
    Emp getEmpAndDept(@Param("eid") Integer eid);
}
<resultMap id="getEmpAndDeptResultMapOne" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
    <!-- association专门处理多对一关系 => property表示需要处理多对一关系的属性名;javaType表示该属性的类型 -->
    <association property="dept" javaType="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
    </association>
</resultMap>
<select id="getEmpAndDept" resultMap="getEmpAndDeptResultMapOne">
    select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>
public class ResultMapTest {
    @Test
    public void testGetEmpAndDept(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp empAndDept = mapper.getEmpAndDept(1);
        System.out.println(empAndDept);
    }
}
  • 分步查询
public interface EmpMapper {
    // &分步查询员工以及员工所对应的部门 => 1.查询员工信息
    Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);
}
<mapper namespace="com.zszy.mybatis.mapper.EmpMapper">
...
    <!-- select 设置分布查询sql的唯一标识 => namespace.sqlId or mapper接口的全类名.方法名 -->
    <!-- column 设置分布查询的条件 -->
    <!-- 延迟加载默认在 mybatis 不开启 -->
    <!-- fetchType 全局开启了延迟加载后可手控延迟加载的效果 => lazy|eager -->
    <resultMap id="empAndDeptByStepOne" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
        <association property="dept" select="com.zszy.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="did" fetchType="lazy"></association>
    </resultMap>
    <!-- Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid); -->
    <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepOne">
        select * from t_emp where eid = #{eid}
    </select>
</mapper>
public interface DeptMapper {
    // &&分步查询员工以及员工所对应的部门 => 2.通过did查询员工对应的部门信息
    Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);
}
<mapper namespace="com.zszy.mybatis.mapper.DeptMapper">
...
    <resultMap id="empAndDeptByStepTwo" type="Dept">
    <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
    </resultMap>
    <!-- Dept getEmpAndDeptByStepTwo(@Param("did") Integer did); -->
    <select id="getEmpAndDeptByStepTwo" resultMap="empAndDeptByStepTwo">
        select * from t_dept where did = #{did}
    </select>
</mapper>
public class ResultMapTest {
    @Test
    public void testGetEmpAndDeptByStep(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp empAndDeptByStepOne = mapper.getEmpAndDeptByStepOne(1);
        System.out.println(empAndDeptByStepOne);
    }
}

一对多映射处理

public class Dept {
    private Integer did;
    private String deptName;
    // 通过集合表示一对多的关系
    private List<Emp> emps;
    ...
}
public interface DeptMapper {
    // 查询部门以及部门中所有的员工信息
    Dept getDeptAndEmp(@Param("did") Integer did);
}
<mapper namespace="com.zszy.mybatis.mapper.DeptMapper">
    <!-- 处理一对多的映射关系 -->
    <!-- 1.collection -->
    <resultMap id="deptAndEmpResultMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
        <!-- collection 处理一对多的映射关系 -->
        <!-- ofType 表示该属性所对应的集合中存储数据的类型 -->
        <collection property="emps" ofType="Emp">
            <id property="eid" column="edi"></id>
            <result property="empName" column="emp_name"></result>
            <result property="age" column="age"></result>
            <result property="sex" column="sex"></result>
            <result property="email" column="email"></result>
        </collection>
    </resultMap>
    <!-- Dept getDeptAndEmp(@Param("did") Integer did); -->
    <select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
        select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
    </select>
</mapper>
public class ResultMapTest {
    @Test
    public void testGetDeptAndEmp(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept deptAndEmp = mapper.getDeptAndEmp(1);
        System.out.println(deptAndEmp);
    }
}
  • 分步查询
public interface DeptMapper {
    // &分步查询查询所有部门以及员工信息 => 1.查询部门信息
    Dept getDeptAndEmpByStepOne(@Param("did") Integer did);
}
<mapper namespace="com.zszy.mybatis.mapper.DeptMapper">
    <!-- 2.分步查询 -->
    <resultMap id="deptAndEmpByStepResultMap" type="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
        <collection property="emps" select="com.zszy.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo" column="did"></collection>
    </resultMap>
    <!-- Dept getDeptAndEmpByStepOne(@Param("did") Integer did); -->
    <select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepResultMap">
        select * from t_dept where did = #{did}
    </select>
</mapper>
public interface EmpMapper {
    // &&分步查询部门以及部门员工 => 2.根据did查询员工信息
    List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);
}
<mapper namespace="com.zszy.mybatis.mapper.EmpMapper">
    ...
    <!-- List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did); -->
    <select id="getDeptAndEmpByStepTwo" resultType="Emp">
        select * from t_emp where did = #{did}
    </select>
</mapper>
public class ResultMapTest {
    @Test
    public void testGetDeptAndEmpByStep(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept deptAndEmpByStepOne = mapper.getDeptAndEmpByStepOne(1);
        System.out.println(deptAndEmpByStepOne);
    }
}

延迟加载

分步查询的优势在于可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息。延迟加载又叫懒加载,也叫按需加载,也就是说先加载主表信息,需要的时候,再去加载从表信息。代码执行查询语句时,并非即时去数据库中查询,而是根据设置的延迟策略推迟查询。

lazyLoadingEnabled => 延迟加载。开启时所有关联对象都会延迟加载。
aggressiveLazyLoading => 按需加载。开启时任何方法的调用都会加载该对象的所有属性。否则每个属性都会按需加载。

此外可通过 association 和 collection 中的 fetchType 属性设置当前的分步查询是否使用延迟加载,fetchType="lazy 延迟加载|eager 立即加载"。

<!-- mybatis-config.xml -->
<configuration>
    <properties resource="jdbc.properties"/>
    <!-- 设置mybatis全局配置 -->
    <settings>
        <!-- 将下划线自动映射为驼峰 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.zszy.mybatis.pojo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.zszy.mybatis.mapper"/>
    </mappers>
</configuration>
public class ResultMapTest {
    ...
    @Test
    public void testGetEmpAndDeptByStepLazyLoadingEnabled(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp empAndDeptByStepOne = mapper.getEmpAndDeptByStepOne(1);
        System.out.println(empAndDeptByStepOne.getEmpName()); // 只会执行查询员工信息的 sql
        System.out.println("---------");
        System.out.println(empAndDeptByStepOne.getDept()); // 执行查询员工以及部门信息的 sql
    }
}

动态 SQL

动态 sql 能根据特定条件动态拼装 sql 语句,常用于处理字符串拼接。

  • if 标签

if 标签通过 test 属性传递过来的数据进行表达式判断,若结果为真,则标签中的内容会执行;反之标签中的内容不会执行。

为避免where 会与 and 连用的报错,可以在 where 后面添加一个恒成立条件1=1 来拼接 and 语句,这种方式不会影响查询的结果。

<mapper namespace="com.zszy.mybatis.mapper.DynamicSQLMapper">
    <select id="getEmpByCondition" resultType="Emp">
        select * from t_emp where 1=1
        <if test="empName != null and empName != ''">and emp_name=#{empName}</if>
        <if test="age !=null and age != ''">and age = #{age}</if>
        <if test="sex !=null and sex != ''">and sex = #{sex}</if>
        <if test="email !=null and email != ''">and email = #{email}</if>
    </select>
</mapper>
  • where 标签

where 和 if 一般结合使用。若 where 标签中的 if 条件都不满足,则 where 标签没有任何功能,也不会添加 where 关键字。当 if 条件满足,才会自动添加 where 关键字,并将条件最前方多余的 and/or 去掉。

<mapper namespace="com.zszy.mybatis.mapper.DynamicSQLMapper">
    <select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <where>
        <!-- emp_name = #{empName} and 不要这么写 => 去不掉 and -->
        <if test="empName != null and empName != ''">emp_name=#{empName}</if>
        <if test="age !=null and age != ''">and age = #{age}</if>
        <if test="sex !=null and sex != ''">and sex = #{sex}</if>
        <if test="email !=null and email != ''">and email = #{email}</if>
    </where>
    </select>
</mapper>
  • trim 标签

prefix => 在 trim 标签中的内容前面添加某些内容。suffix => 在 trim 标签中的内容后面添加某些内容。prefixOverrides => 在 trim 标签中的内容前面去掉某些内容。suffixOverrides => 在 trim 标签中的内容后面去掉某些内容。若 trim 中的标签都不满足条件 => trim 标签没有任何效果。

<mapper namespace="com.zszy.mybatis.mapper.DynamicSQLMapper">
    <select id="getEmpByCondition" resultType="Emp">
        select * from t_emp
        <trim prefix="where" suffixOverrides="and|or">
            <if test="empName != null and empName != ''">emp_name=#{empName} and</if>
            <if test="age !=null and age != ''">age = #{age} and</if>
            <if test="sex !=null and sex != ''">sex = #{sex} and</if>
            <if test="email !=null and email != ''">email = #{email} and</if>
        </trim>
    </select>
</mapper>
  • choose(when、otherwise) 标签与 SQL 片段

choose(when、otherwise) 相当于 if...else,when 至少要有一个,otherwise 至多只有一个。在确定一个 when 成立后,不再执行后续操作。

sql 片段可以记录一段公共 sql 片段,在使用的地方通过 include 标签进行引入。

public interface DynamicSQLMapper {
    // 测试 choose、when、otherwise
    List<Emp> getEmpByChoose(Emp emp);
}
<mapper namespace="com.zszy.mybatis.mapper.DynamicSQLMapper">
    <sql id="empColumns">eid,emp_name,age,sex,email</sql>
    <select id="getEmpByChoose" resultType="Emp">
        select <include refid="empColumns"></include> from t_emp
        <where>
            <choose>
                <when test="empName != null and empName != ''">emp_name=#{empName}</when>
                <when test="age != null and age != ''">age=#{age}</when>
                <when test="sex != null and sex != ''">sex=#{sex}</when>
                <when test="email != null and email != ''">email=#{email}</when>
                <otherwise> did = 1 </otherwise>
            </choose>
        </where>
    </select>
</mapper>
public class DynamicSQLMapperTest {
    @Test
    public void testGetEmpByChoose(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        List<Emp> list = mapper.getEmpByChoose(new Emp(null, null, null, null, null));
        System.out.println(list);
    }
}
  • foreach 标签

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

属性 collection => 设置要循环的数组或集合。item => 集合或数组中的每一项数据。separator => 设置循环体之间的分隔符,分隔符前后默认有一个空格。open => 设置 foreach 循环的外部开始符。close => 设置 foreach 循环的外部结束符。

public interface DynamicSQLMapper {
    // 数组实现批量删除
    int deleteMoreByArray(@Param("eids") Integer[] eids);
}
<mapper namespace="com.zszy.mybatis.mapper.DynamicSQLMapper">
    <!-- int deleteMoreByArray(@Param("eids") Integer[] eids); -->
    <delete id="deleteMoreByArray">
        <!-- 方式一 => 括号包裹循环内容 -->
        <!-- delete from t_emp where eid in -->
        <!-- ( -->
        <!-- <foreach collection="eids" item="eid" separator=","> -->
        <!-- #{eid} -->
        <!-- </foreach> -->
        <!-- ) -->
        <!-- 方式二 => open + close 代替括号 -->
        <!-- delete from t_emp where eid in -->
        <!-- <foreach collection="eids" item="eid" separator="," open="(" close=")"> -->
        <!-- #{eid} -->
        <!-- </foreach> -->
        delete from t_emp where
        <foreach collection="eids" item="eid" separator="or">
            eid=#{eid}
        </foreach>
    </delete>
</mapper>
public class DynamicSQLMapperTest {
    @Test
    public void testDeleteMoreByArray(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        int i = mapper.deleteMoreByArray(new Integer[]{7, 8});
        System.out.println(i);
    }
}
public interface DynamicSQLMapper {
    // 集合实现批量添加
    int insertMoreByList(@Param("emps") List<Emp> emps);
}
<mapper namespace="com.zszy.mybatis.mapper.DynamicSQLMapper">
    <!-- int insertMoreByList(@Param("emps") List<Emp> emps); -->
    <insert id="insertMoreByList">
        insert into t_emp values
        <foreach collection="emps" item="emp" separator=",">
            (null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null)
        </foreach>
    </insert>
</mapper>
public class DynamicSQLMapperTest {
    @Test
    public void testInsertMoreByList(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        Emp emp1 = new Emp(null,"test1",18,"男","test@mail.com");
        Emp emp2 = new Emp(null,"test2",18,"女","test@mail.com");
        List<Emp> emps = Arrays.asList(emp1, emp2);
        System.out.println(mapper.insertMoreByList(emps));
    }
}

MyBatis 缓存

一级缓存(默认开启)

一级缓存属于 SqlSession 级别,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,而不会从数据库重新访问。

一级缓存失效情况 => 不同 SqlSession 对应不同一级缓存;同一 SqlSession 但查询条件不同;同一 SqlSession 两次查询期间执行了增删改操作;同一 SqlSession 两次查询期间手动清空缓存。

public interface CacheMapper {
    // 缓存只针对查询
    Emp getEmpByEid(@Param("eid") Integer eid);
    void insertEmp(Emp emp);
}
<mapper namespace="com.zszy.mybatis.mapper.CacheMapper">
    <!-- Emp getEmpByEid(@Param("eid") Integer eid); -->
    <select id="getEmpByEid" resultType="Emp">
        select * from t_emp where eid = #{eid}
    </select>
</mapper>
public class CacheMapperTest {
    @Test
    public void testFirstLevelCache(){
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        // 同一个 sqlSession 查询的数据会被缓存
        CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
        // 只会输出一个 sql 取决于是否手动清空缓存 sqlSession.clearCache(); or 增删改
        Emp emp1 = mapper.getEmpByEid(1);
        System.out.println(emp1);
//        mapper.insertEmp(new Emp(null,"abc",24,"女","xc@mail.com"));
//        sqlSession.clearCache();
        Emp emp2 = mapper.getEmpByEid(1);
        System.out.println(emp2);
    }
}

二级缓存

二级缓存属于 SqlSessionFactory 级别,同 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果会从缓存中获取。

=> 二级缓存开启的条件
配置文件中设置全局配置属性 cacheEnabled="true"(默认为 true);映射文件中设置标签 <cache />;二级缓存必须在 SqlSession 关闭或提交之后有效;查询的数据所转换的实体类 pojo 必须实现序列化 Serializable 的接口。

二级缓存失效 => 两次查询间执行了增删改,此时一级缓存一并失效。

public class Emp implements Serializable {...}
<mapper namespace="com.zszy.mybatis.mapper.CacheMapper">
    <!-- 开启二级缓存 -->
    <cache />
    ...
</mapper>
public class CacheMapperTest {
    @Test
    public void testSecondLevelCache(){
        // 不能使用 SqlSessionUtils.getSqlSession() 的原因在于源码中每次调用此方法都会创建新的 sqlSessionFactory
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
            CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
            System.out.println(mapper1.getEmpByEid(1));
            sqlSession1.close();
            SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
            CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
            System.out.println(mapper2.getEmpByEid(1));
            sqlSession2.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
cache 标签属性 说明
eviction 缓存回收策略 => LRU Least Recently Used 最近最少使用、FIFO First in First out 先进先出、SOFT 软引用、WEAK 弱引用
flushInterval 刷新间隔 => 单位毫秒 -> 默认情况不设,没有刷新间隔,缓存仅仅调用语句(增删改)时刷新
size 引用数目 => 正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly 只读 => true 给所有调用者返回缓存对象的相同实例 /false 会返回通过序列化缓存对象的拷贝(安全)

缓存查询顺序

首先进行二级缓存查询。在二级缓存中可能存有其他程序已查出的数据,方便直接使用。如果二级缓存没有命中,再查询一级缓存。如果一级缓存也没有命中,则查询数据库。SqlSession 关闭之后,一级缓存中的数据会写入二级缓存。由于一级缓存需要关闭后才会提交给二级缓存,那么二级缓存没有的可能一级缓存会有。

第三方缓存 EHCache

EhCache 是进程内缓存框架,也是 Hibernate 中的默认缓存框架,可作为 mybatis 的二级缓存补充。要注意的是,一级缓存没办法代替。

  • Maven 依赖包
<!-- Mybatis EHCache 整合包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>
<!-- slf4j "撒拉风街"日志门面的一个具体实现 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
  • ehcache.xml
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 => ehcache 缓存的数据保存于磁盘 -->
    <diskStore path="/Users/xxx/Desktop/ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>
  • 设置二级缓存的类型
<!-- xxxMapper.xml -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  • logback 日志

存在 SLF4J 时,作为简易日志的 Log4j 将失效,此时需要借助 SLF4J 的具体实现 logback 来打印日志。logback 的配置文件固定名为 logback.xml。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
        </encoder>
    </appender>
    <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.xxx.crowd.mapper" level="DEBUG"/>
</configuration>
属性名 是否必须 作用
maxElementsInMemory 在内存中缓存的 Element 的最大数目
maxElementsOnDisk 在磁盘上缓存的 Element 的最大数目 => 0 表示无穷大
eternal 缓存的 Elements 是否永远不过期;true => 缓存的数据始终有效;false => 根据 timeToIdleSeconds、timeToLiveSeconds 判断
overflowToDisk 当内存缓存溢出的时候是否将过期的 Element 缓存到磁盘上
timeToIdleSeconds 当缓存在 EhCache 中的数据前后两次访问的时间超过 timeToIdleSeconds 的属性取值时数据会删除 => 默认值是 0;也就是可闲置时间无穷大
timeToLiveSeconds 缓存 Element 的有效生命期 => 默认是 0 -> Element 存活时间无穷大
diskSpoolBufferSizeMB DiskStore 磁盘缓存的缓存区大小 => 默认是 30MB -> 每个 Cache 都应该有自己的一个缓冲区
diskPersistent 在 VM 重启的时候是否启用磁盘保存 EhCache 中的数据 => 默认是 false
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔;默认是 120s。每个 120s 相应的线程会进行一次 EhCache 中数据的清理工作
memoryStoreEvictionPolicy 当内存缓存达到最大且又有新的 Element 加入时,移除缓存中 Element 的策略。默认 LRU(最近最少使用),可选 LFU(最不常使用)和 FIFO(先进先出)

插件工具

MBG MyBatis Generator 逆向工程

正向工程是先创建实体类,再由框架根据实体类生成数据库表。逆向工程是先创建数据库表,再由框架根据数据库表反向生成 Java 实体类、Mapper 接口、Mapper 映射文件。

  • 依赖和插件
...
<packaging>jar</packaging>
...
<dependencies>
    <!-- MyBatis核心依赖包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.27</version>
    </dependency>
    <!-- log4j日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                    <version>0.9.2</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.27</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
  • 配置文件

MBG 逆向工程其实与配置文件关系不大,主要是为搭建数据库环境。mybatis-config.xml 与 log4j.xml 配置同上。

<!-- jdbc.properties -->
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/MyBatis?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
jdbc.username=<username>
jdbc.password=<password>
<!-- 逆向工程配置文件 => 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">
<generatorConfiguration>
    <!--
    targetRuntime => 执行生成的逆向工程的版本
    MyBatis3Simple => 生成基本的 CRUD
    MyBatis3 => 生成带条件的 CRUD
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 数据库的连接信息 => 连接数据库进行解析表 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="<username>"
                        password="<password>">
        </jdbcConnection>
        <!-- javaBean 的生成策略-->
        <javaModelGenerator targetPackage="com.zszy.mybatis.pojo" targetProject="./src/main/java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL 映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.zszy.mybatis.mapper" targetProject="./src/main/resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper 接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.zszy.mybatis.mapper" targetProject="./src/main/java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号 => 对应所有表 => 此时不写 domainObjectName -->
        <!-- domainObjectName 属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>
  • 逆向操作

执行 Maven 中 MBG 插件的 mybatis-generator:generate。

public class MBGTest {
    @Test
    public void testMBG(){
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
            // 查询所有数据
//            List<Emp> list = mapper.selectByExample(null);
//            list.forEach(emp -> System.out.println(emp));
            // 根据条件查询
//            EmpExample example = new EmpExample();
            // QBC 风格 => query by Criteria 
//            example.createCriteria().andEmpNameEqualTo("zs").andAgeGreaterThan(20);
//            example.or().andDidIsNotNull();
//            List<Emp> list = mapper.selectByExample(example);
//            list.forEach(emp -> System.out.println(emp));
            // 修改 => updateByPrimaryKeySelective => 某个属性为 null 则不会出现在修改列表中
            mapper.updateByPrimaryKeySelective(new Emp(10,"testMBG",22,"女","testMBG@mail.com",3));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

分页插件

  • 添加依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>5.2.0</version>
</dependency>
  • mybatis-config.xml 配置分页插件
...
<plugins>
    <!-- 设置分页插件 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
  • 开启分页功能

在查询功能之前使用 PageHelper.startPage(int pageNum, int pageSize) 开启分页功能,pageNum 表示当前页的页码,pageSize 表示每页显示的条数。

public class PageHelperTest {
    @Test
    public void testPageHelper(){
        /*
        index => 当前页的起始索引
        pageSize => 每页显示条数
        pageNum => 当前页的页码
        index = (pageNum-1)*pageSize
         */
        // 查询功能前开启分页;查询功能之后获取分页相关信息
        InputStream is = null;
        try {
            is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
            // Page<Object> page = PageHelper.startPage(1, 4);
            PageHelper.startPage(1, 4);
            List<Emp> list = mapper.selectByExample(null);
//            list.forEach(emp -> System.out.println(emp));
            // PageInfo 常应用于居中页面条
            PageInfo<Emp> page = new PageInfo<>(list,3);
            System.out.println(page);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Bugs 解决

  • BeanCreationException => Error creating bean with name 'sqlSessionFactory'
// .../logs/iamericanoLogs
// 日志直接看时间首段
00:00:00.000 [RMI TCP Connection(5)-127.0.0.1] ERROR o.s.w.c.ContextLoader - [initWebApplicationContext,313] - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [application.xml]: Initialization of bean failed; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'org.springframework.core.io.Resource[]' for property 'mapperLocations'; nested exception is java.lang.IllegalArgumentException: Could not resolve resource location pattern [classpath:mapper/**/*.xml]: class path resource [mapper/] cannot be resolved to URL because it does not exist
// 解决的报错 => 因映射文件不存在而导致的副作用
// 23-Jun-2020 21:46:22.000 SEVERE [RMI TCP Connection(5)-127.0.0.1] org.apache.catalina.core.StandardContext.startInternal One or more listeners failed to start. Full details will be found in the appropriate container log file
// 23-Jun-2020 21:46:22.000 SEVERE [RMI TCP Connection(5)-127.0.0.1] org.apache.catalina.core.StandardContext.startInternal Context [/admin] startup failed due to previous errors

'sqlSessionFactory' Bean 创建失败指向映射文件的缺失,在搭建项目初期出现此问题时可先注释掉属性 mapperLocations 的相关代码进行启动。

主要注意点是 mapper 目录下相关映射文件的存在,以及因此原因导致的外层报错 startInternal One or more listeners failed to start。

<!-- application.xml -->
...
<!-- 整合围绕 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    ...
    <property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
</bean>
  • BindingException => Invalid bound statement (not found)
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.zszy.dao.IamericanoUserDao.queryById

EasyCode 逆向工程生成 resources/mapper 下的 xxxMapper.xml 文件时失效。没有映射文件那么必然会绑定失败。

mybatis – MyBatis 3 | 简介

结束

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!