MyBatis
快速上手
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&useUnicode=true&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 中设置属性 useGeneratedKeys
和 keyProperty
。因为增删改有统一受影响的行数的返回值,因此只能将获取的自增主键放在传输的参数对象中的某个属性。
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 文件时失效。没有映射文件那么必然会绑定失败。
结束
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!