Starter
DB Data
- pe_department
CREATE TABLE `pe_department` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, `remark` VARCHAR(100) NULL DEFAULT NULL, PRIMARY KEY (`id`) ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB AUTO_INCREMENT=4 ;
- pe_employee
CREATE TABLE `pe_employee` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `department_id` INT(11) NOT NULL, `name` VARCHAR(50) NOT NULL DEFAULT '0', `remark` VARCHAR(100) NULL DEFAULT NULL, PRIMARY KEY (`id`), INDEX `FK__pe_department` (`department_id`), CONSTRAINT `FK__pe_department` FOREIGN KEY (`department_id`) REFERENCES `pe_department` (`id`) ) COLLATE='latin1_swedish_ci' ENGINE=InnoDB AUTO_INCREMENT=10 ;
test data
-- pe_department INSERT INTO pe_department(name,remark) VALUES('Dep-A','This is Department A'); INSERT INTO pe_department(name,remark) VALUES('Dep-B','This is Department B'); INSERT INTO pe_department(name,remark) VALUES('Dep-C','This is Department C'); -- pe_employee INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test1',1,'This is Test1'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test2',1,'This is Test2'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test3',1,'This is Test3'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test4',2,'This is Test4'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test5',2,'This is Test5'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test6',2,'This is Test6'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test7',3,'This is Test7'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test8',3,'This is Test8'); INSERT INTO pe_employee(name,department_id,remark) VALUES ('Test9',3,'This is Test9');
HelloWorld
- dependency (pom.xml)
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </dependency>
global config (mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/EmployeeMapper.xml"/> </mappers> </configuration>
sql config (mapper/EmployeeMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cj.mybatis.mapper.EmployeeMapper"> <!-- id: 标识; resultType: 返回值类型; #{id}: 从传递过来的参数中取出id值 --> <select id="getEmployee" resultType="com.cj.mybatis.entity.Employee"> select id,name,remark,department_id as departmentId from pe_employee where id = #{id} </select> </mapper>
entity (com.cj.mybatis.entity.Employee.java)
public class Employee { private Integer id; private String name; private String remark; private Integer departmentId; // getXxx,setXxx,toString // ... }
Usage1
@Test public void test1() throws IOException { // 1. 根据全局配置文件创建一个SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); // 2. 获取SqlSession对象来执行映射的sql(一个SqlSession代表和数据库的一次会话,用完关闭) SqlSession session = sqlSessionFactory.openSession(); try { // selectOne(namespace.id,parameter) Employee emp = session.selectOne("com.cj.mybatis.mapper.EmployeeMapper.getEmployee", 1); System.out.println(emp); }finally { session.close(); } }
Usage2
dao interface (com.cj.mybatis.mapper.EmployeeMapper.java)
package com.cj.mybatis.mapper; import com.cj.mybatis.entity.Employee; public interface EmployeeMapper { Employee getEmployee(Integer id); }
Test
@Test public void test2() throws IOException { // 1. 根据全局配置文件创建一个SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); // 2. 获取SqlSession对象,执行映射的sql try (SqlSession session = sqlSessionFactory.openSession()) { // Method1: no EmployeeMapper.java // Employee emp = session.selectOne("com.cj.mybatis.mapper.EmployeeMapper.getEmployee", 1); // Method2: use EmployeeMapper.java // 注: interface EmployeeMapper 无实现类,mybatis会将此接口和sql配置进行绑定,生成一个代理对象执行 EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); Employee emp = employeeMapper.getEmployee(1); System.out.println(emp); }
Global Config
- properties
- settings
- typeAliases
- typeHandlers
- objectFactory
- plugins
- evironments (evironment: transactionManager & dataSource)
- databaseIdProvider
- mappers
properties
Sample:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- resource: 引入类路径下资源; url: 引入网络或磁盘路径下资源 --> <properties resource="db.properties"> <!-- <property name="" value=""/> --> </properties> <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> <mapper resource="mapper/EmployeeMapper.xml"/> <mapper resource="mapper/DepartmentMapper.xml"/> </mappers> </configuration>
- db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC jdbc.username=root jdbc.password=123456
settings
- cacheEnable (default: true)
- lazyLoadingEnabled (default: false)
- useColumnLabel (default: true)
- defaultStatementTimeout (default: null)
- mapUnderscoreToCamelCase (default: false 默认不开启驼峰命名规则映射:a_column->aColumn)
- jdbcTypeForNull (default:OTHER)
- autoMappingBehavior (default: PARTIAL 开启自动映射,列名与javaBean属性名必须一致)
- ...
Sample:
- mybatis-config.xml
<settings> <!-- 打开驼峰命名规则映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
- mapper/EmployeeMapper.xml
<mapper namespace="com.cj.mybatis.mapper.EmployeeMapper"> <select id="getEmployee" resultType="com.cj.mybatis.entity.Employee"> <!-- select id,name,remark,department_id as departmentId from pe_employee where id = #{id} --> select * from pe_employee where id = #{id} <!-- => setting mapUnderscoreToCamelCase:true后,可以匹配department_id到departmentId--> </select> </mapper>
typeAliases
- 别名处理器,给java类型起别名(别名不区分大小写)
- There are many built-in type aliases for common Java types,eg:
- byte _byte
- int _int
- String string
- Integer int/integer
- Date date
- Map map
- ...
Sample:
单个配置别名(使用默认别名)
<!-- mybatis-config.xml --> <typeAliases> <typeAlias type="com.cj.mybatis.entity.Employee"/> <!-- 默认别名=类名 --> </typeAliases> <!-- mapper/EmployeeMapper.xml --> <mapper namespace="com.cj.mybatis.mapper.EmployeeMapper"> <select id="getEmployee" resultType="employee"> <!-- employee/Employee/com.cj.mybatis.entity.Employee 都可--> select * from pe_employee where id = #{id} </select> </mapper>
单个配置别名(指定别名)
<!-- mybatis-config.xml --> <typeAliases> <typeAlias type="com.cj.mybatis.entity.Employee" alias="emp"/> <!-- 指定别名 --> </typeAliases> <!-- mapper/EmployeeMapper.xml --> <mapper namespace="com.cj.mybatis.mapper.EmployeeMapper"> <select id="getEmployee" resultType="emp"> <!-- emp/Emp/com.cj.mybatis.entity.Employee 都可--> select * from pe_employee where id = #{id} </select> </mapper>
批量配置别名
<!-- mybatis-config.xml --> <typeAliases> <!-- <typeAlias type="com.cj.mybatis.entity.Department"/> <typeAlias type="com.cj.mybatis.entity.Employee" alias="emp"/> --> <!-- 批量起别名(使用默认别名,即类名) 在此基础上,可在类上加@Alias注解为某个类指定新的别名,eg: @Alias("emp") public Employee{ //... } --> <package name="com.cj.mybatis.entity"/> </typeAliases>
typeHandlers
类型处理器(XxxTypeHandler): value => javaType
<typeHandlers>
<!-- method1: 配置具体的TypeHandler,eg:
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.cj.mybatis.entity.UserTypeEnum"/>
-->
<!-- method2: 或者在XxxMapper.xml中具体处理某个字段时设置,eg:
#{status,typeHandler=xxx}
<resultMap>
<result column="" property="" typeHandler="xxx" />
</resultMap>
注:若在位置参数中配置typeHandler,应保证insert/select用的TypeHandler是一样的
-->
</typeHandlers>
plugins
Sample:
<plugins>
<plugin interceptor="com.cj.mybatis.extend.MyFirstPlugin">
<property name="testname" value="first-plugin"/>
</plugin>
</plugins>
environments
Sample:
<environments default="development"> <!-- switch: development/product-->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${dev.jdbc.driver}"/>
<property name="url" value="${dev.jdbc.url}"/>
<property name="username" value="${dev.jdbc.username}"/>
<property name="password" value="${dev.jdbc.password}"/>
</dataSource>
</environment>
<environment id="product">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${pro.jdbc.driver}"/>
<property name="url" value="${pro.jdbc.url}"/>
<property name="username" value="${pro.jdbc.username}"/>
<property name="password" value="${pro.jdbc.password}"/>
</dataSource>
</environment>
</environments>
databaseIdProvider
支持多数据库厂商(execute different statements depending on database vendor)
<!-- mybatis-config.xml -->
<!-- type="DB_VENDOR": VendorDatabaseIdProvider 得到数据库厂商的不同标识(驱动 Connection.getMetaData().getDatabaseProductName()) -->
<databaseIdProvider type="DB_VENDOR">
<!-- 为不同数据库厂商标识起个别名 -->
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
<!-- mapper/EmployeeMapper.xml -->
<mapper namespace="com.cj.mybatis.mapper.EmployeeMapper">
<!-- 1. default -->
<select id="getEmployee" resultType="com.cj.mybatis.entity.Employee">
select * from pe_employee where id = #{id}
</select>
<!-- 2. for mysql -->
<select id="getEmployee" resultType="com.cj.mybatis.entity.Employee" databaseId="mysql">
select * from pe_employee where id = #{id}
</select>
<!-- 3. for oracle -->
<select id="getEmployee" resultType="com.cj.mybatis.entity.Employee" databaseId="oracle">
select * from pe_employee where id = #{id}
</select>
</mapper>
<!--
=> if current use MySQL datasource => load 1 & 2 => use 2
=> if current use Oracle datasource => load 1 & 3 => use 3
=> if current use any Other datasource => load 1 => use 1
-->
mappers
注册sql映射到global config中
<mapper>
单个注册:- url: 磁盘或网络文件(file:///var/mappers/AuthorMapper.xml)
- resource: 类路径下文件 (mapper/EmployeeMapper.xml)
- class: 接口
- 方式一:接口和mapper文件同名同路径下
- 方式二:接口上使用注解写sql(无需mapper文件)
<package>
批量注册- name: 接口包名(接口使用注解写sql)
Sample:三种方式
mybatis-config.xml
- 配置使用url/resource & mapper文件
<mappers> <mapper resource="mapper/EmployeeMapper.xml"/> </mappers>
package com.cj.mybatis.mapper; import com.cj.mybatis.entity.Employee; public interface EmployeeMapper { Employee getEmployee(Integer id); }
- 配置使用class & 接口上使用注解写sql(无需mapper文件)
<mappers> <mapper class="com.cj.mybatis.mapper.EmployeeWithAnnotationMapper"/> </mappers>
package com.cj.mybatis.mapper; import org.apache.ibatis.annotations.Select; import com.cj.mybatis.entity.Employee; public interface EmployeeWithAnnotationMapper { @Select("select * from pe_employee where id=#{id}") public Employee getEmployee(Integer id); }
- 批量注册(规则同class方式)
<mappers> <package name="com.cj.mybatis.mapper"/> </mappers>
Sql Mapper Config: CRUD
<select>
<insert>
<update>
<delete>
select
<select>
用来定义查询操作,属性:
- 唯一标识: id
- 参数
- parameterType 参数类型(optional,mybatis会根据TypeHandler自动推断)
- 结果集
- resultType 返回值类型,别名或全类名(集合类则指定集合中的元素类型),注:不能和resultMap同时使用
- resultMap
- resultSets,resultSetType
- resultOrdered
- 缓存相关
- flushCache
- useCache
- statementType: PREPARED,STATEMENT,CALLABLE
- timeout
- fetchSize
- databaseId
DepartmentMapper.xml
<!-- public Department getDepartment(Integer id); --> <select id="getDepartment" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where id = #{id} </select> <!-- public List<Department> listDepartment(); --> <select id="listDepartment" resultType="com.cj.mybatis.entity.Department"> select * from pe_department </select>
test
private SqlSessionFactory getSqlSessionFactory() throws IOException { String resource = "mybatis-config2.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); return new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testQueryDepartment() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); System.out.println("------------listDepartment------------"); List<Department> list = departmentMapper.listDepartment(); System.out.println(list); System.out.println("------------getDepartment------------"); Department dept = departmentMapper.getDepartment(1); System.out.println(dept); } }
insert
DepartmentMapper.xml
<!-- public Integer insertDepartment(Department department); --> <insert id="insertDepartment"> insert into pe_department(name,remark) values (#{name},#{remark}) </insert> <!-- public Integer insertDepartmentAndReturnId(Department department); --> <!-- useGeneratedKeys=false/true 是否使用产生的主键; keyProperty: 配置获取到的主键注入javaBean的哪个属性中 --> <insert id="insertDepartmentAndReturnId" useGeneratedKeys="true" keyProperty="id"> insert into pe_department(name,remark) values (#{name},#{remark}) </insert> <!-- public Integer insertDepartmentByNextSeq(Department department); --> <insert id="insertDepartmentByNextSeq"> <!-- order="BEFORE"/"AFTER" 在sql执行前/后执行 --> <!-- <selectKey keyProperty="id" order="BEFORE" resultType="Integer"> select max(id)+1 from pe_department </selectKey> --> <selectKey keyProperty="id" order="BEFORE" resultType="Integer"> <!-- 注意加allowMultiQueries=true这里才可执行多条( jdbc:mysql://localhost:3306/demo?allowMultiQueries=true) --> update pe_generator set next=next+1 where segment_name='dept_seq'; select next from pe_generator where segment_name='dept_seq' </selectKey> insert into pe_department(id,name,remark) values (#{id},#{name},#{remark}) </insert>
test
/* Note: CUD 可返回类型:Integer/Long/Boolean 受影响的行数/是否成功 CUD 需提交数据: factory.openSession(true) 自动commit / factory.openSession() + session.commit() 手动commit */ @Test public void testInsertDepartment() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); System.out.println("------------insertDepartment------------"); Department dept = new Department("Dep-test01","This is department test01"); Integer result = departmentMapper.insertDepartment(dept); System.out.println(result); System.out.println(dept); // => dept.getId() is null session.commit(); // note: commit } } @Test public void testInsertDepartmentAndReturnId() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); System.out.println("------------insertDepartmentAndReturnId------------"); Department dept = new Department("Dep-test01","This is department test01"); Integer result = departmentMapper.insertDepartmentAndReturnId(dept); System.out.println(result); System.out.println(dept); // => dept.getId() is not null session.commit(); // note: commit } } @Test public void testInsertDepartmentByNextSeq() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); System.out.println("------------insertDepartmentAndReturnId------------"); Department dept = new Department("Dep-test01","This is department test01"); Integer result = departmentMapper.insertDepartmentByNextSeq(dept); System.out.println(result); System.out.println(dept); // => dept.getId() is not null session.commit(); // note: commit } }
update
DepartmentMapper.xml
<!-- public Integer updateDepartment(Department department); --> <update id="updateDepartment"> update pe_department set name=#{name},remark=#{remark} where id=#{id} </update>
test
@Test public void testUpdateDepartment() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); System.out.println("------------updateDepartment------------"); Integer result = departmentMapper.updateDepartment(new Department(5,"Dep-test02","This is department test02")); System.out.println(result); session.commit(); // note: commits } }
delete
DepartmentMapper.xml
<!-- public Integer deleteDepartment(Integer id); --> <delete id="deleteDepartment"> delete from pe_department where id=#{id} </delete>
test
@Test public void testDeleteDepartment() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); System.out.println("------------deleteDepartment------------"); Integer result = departmentMapper.deleteDepartment(5); System.out.println(result); session.commit(); // note: commits } }
ParameterHandler
Parameters
单个参数(mybatis一般不做特殊处理)
<!-- 传入基础类型Integer: public Department getDepartment(Integer id); --> <select id="getDepartment" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where id = #{id} </select> <!-- 传入POJO : public List<Department> listByExample(Department department); --> <select id="listByExample" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where name like #{name} or remark like #{remark} </select> <!-- 传入Map : public List<Department> listByMap(Map params); --> <select id="listByMap" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where name like #{name} or remark like #{remark} </select> <!-- 传入Collection/List/Set/List/Array : 会做特殊处理(封装到Map中) Collection => Map<collection,Object> List => Map<list,Object> Set => Map<set,Object> Array => Map<array,Object> public List<Department> listByIds(List<Integer> ids); --> <select id="listByIds" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where id in (#{list[0]}) </select>
多个参数(mybatis会做特殊处理) => 会被封装成一个
Map<String,Object>
(key: param1,..,paramN),#{}
即从map中取值<!-- public List<Department> listByParams1(String name,String remark); => available parameters are [arg0,arg1,param1,param2] --> <select id="listByParams1" resultType="com.cj.mybatis.entity.Department"> <!-- select * from pe_department where name like #{arg0} or remark like #{arg1} --> select * from pe_department where name like #{param1} or remark like #{param2} </select> <!-- 使用@Param注解命名参数 public List<Department> listByParams2(@Param("name")String name,@Param("remark")String remark); => available parameters are [name,remark,param1,param2] --> <select id="listByParams2" resultType="com.cj.mybatis.entity.Department"> <!-- select * from pe_department where name like #{param1} or remark like #{param2} --> select * from pe_department where name like #{name} or remark like #{remark} </select>
#{}
,${}
#{}
vs${}
<!-- ${} : 直接拼接到sql语句中 => select * from pe_department where id = 2 --> <select id="getDepartment" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where id = ${id} <!-- 更多应用场景(在sql中不支持占位符的地方使用): select * from ${year}_salary select * from pe_employee order by ${orderColumn} --> </select> <!-- #{} : 预编译模式注入参数(PreparedStatement 占位符),防止sql注入 => select * from pe_department where id = ? --> <select id="getDepartment" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where id = #{id} </select>
#{}
中可设置参数规则javaType
指定参数类型(通常可根据参数对象自动确定)jdbcType
(eg:#{remark,jdbcType=NULL}
)mode
numericScale
resultMap
typeHandler
jdbcTypeName
expression
ResultSetHandler
resultType
Sample: DepartmentMapper.xml
get single result
<!-- public Department getDepartment(Integer id); resultType: Department --> <select id="getDepartment" resultType="com.cj.mybatis.entity.Department"> select * from pe_department where id = #{id} </select> <!-- public Map<String,Object> getDepartmentMap(Integer id); => resultType: map --> <select id="getDepartmentMap" resultType="map"> select * from pe_department where id = #{id} </select>
@Test public void testReturnSingle() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); // return POJO (set resultType="com.cj.mybatis.entity.Department") System.out.println("------------getDepartment------------"); Department dept = departmentMapper.getDepartment(1); System.out.println(dept); // return Map (set resultType="map") System.out.println("------------getDepartmentMap------------"); Map<String,Object> deptMap = departmentMapper.getDepartmentMap(1); System.out.println(deptMap); } }
get multiple results
<!-- public List<Department> listDepartment(); => resultType : Department (集合类则指定集合中的元素类型) --> <select id="listDepartment" resultType="com.cj.mybatis.entity.Department"> select * from pe_department </select> <!-- @MapKey("id") // 将结果集封装成Map public Map<Integer,Department> listAndReturnMap(); => resultType : Department (集合类则指定集合中的元素类型) --> <select id="listDepartmentMap" resultType="com.cj.mybatis.entity.Department"> select * from pe_department; </select>
@Test public void testReturnMultiple() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class); // return List<Department> (set resultType="com.cj.mybatis.entity.Department") System.out.println("------------listDepartment------------"); List<Department> list = departmentMapper.listDepartment(); System.out.println(list); // return Map<Integer,Department> (set resultType="com.cj.mybatis.entity.Department") // + @MapKey("") 注解 => 将结果集封装成Map System.out.println("------------listDepartmentMap------------"); Map<Integer,Department> map = departmentMapper.listDepartmentMap(); System.out.println(map); } }
resultMap
自定义resultMap,实现高级结果集映射
<resultMap>
- properties:
id
: 唯一标识type
: javaBean
- children:
<id column="" property=""/>
定义主键列映射规则,有利底层优化 (column
:列名,property
:javaBean属性名)<result column="" property=""/>
定义其他普通列映射规则<association>
定义关联单个对象的映射规则<association property="" javaType="" autoMapping="" columnPrefix="" />
<association property="" select="" column="" fetchType="" autoMapping="" columnPrefix="" />
分段查询
<collection>
定义关联多个对象的映射规则<collection property="" ofType="" autoMapping="" columnPrefix="" />
(ofType
指定集合里面的元素类型)<collection property="" select="" column="" fetchType="" autoMapping="" columnPrefix="" />
分段查询
- 注分段查询中:
select
指定当前属性通过调用哪个方法获取column
指定将那几个列值传递给这个方法作为参数(单列:column="xxx"
; 多列:column={key=column1,key=column2}
)fetchType
: eager/lazy- 即使用
select
指定的方法(传入column
指定的列参数值)查出结果,封装给property
指定的javaBean属性
resultMap:association
OneToOne
Sample:EmployeeWithDepartmentMapper.xml
- Pojo: EmployeeWithDepartment
@Data public class EmployeeWithDepartment { private Integer id; private String name; private String remark; private Department department; }
mapper: EmployeeWithDepartmentMapper.xml
<mapper namespace="com.cj.mybatis.mapper.EmployeeWithDepartmentMapper"> <!-- <resultMap type="com.cj.mybatis.entity.EmployeeWithDepartment" id="EmpWithDeptMap"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="remark" property="remark"/> <association property="department" javaType="com.cj.mybatis.entity.Department" > <id column="dept_id" property="id"/> <result column="dept_name" property="name"/> <result column="dept_remark" property="remark" /> </association> </resultMap> --> <resultMap id="EmpWithDeptMap" type="com.cj.mybatis.entity.EmployeeWithDepartment" autoMapping="true"> <association property="department" javaType="com.cj.mybatis.entity.Department" columnPrefix="dept_" autoMapping="true" /> </resultMap> <select id="getEmployee" resultMap="EmpWithDeptMap"> select a.id id,a.name name,a.remark remark, b.id dept_id,b.name dept_name,b.remark dept_remark from pe_employee a left join pe_department b on a.department_id=b.id where a.id = #{id} </select> <resultMap type="com.cj.mybatis.entity.EmployeeWithDepartment" id="EmpWithDeptLazyMap"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="remark" property="remark"/> <association property="department" select="com.cj.mybatis.mapper.DepartmentMapper.getDepartment" column="department_id" fetchType="lazy"> </association> </resultMap> <select id="getEmployeeLazy" resultMap="EmpWithDeptLazyMap"> select * from pe_employee where id = #{id} </select> </mapper>
test
@Test public void testEmployeeWithDepartment() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ EmployeeWithDepartmentMapper edMapper = session.getMapper(EmployeeWithDepartmentMapper.class); System.out.println("------------getEmployee------------"); EmployeeWithDepartment emp = edMapper.getEmployee(1); System.out.println(emp); System.out.println("------------getEmployeeLazy------------"); emp = edMapper.getEmployeeLazy(1); System.out.println(emp.getName()); System.out.println(emp.getDepartment()); System.out.println(emp); } }
- result
------------getEmployee------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select a.id id,a.name name,a.remark remark, b.id dept_id,b.name dept_name,b.remark dept_remark from pe_employee a left join pe_department b on a.department_id=b.id where a.id = ? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 EmployeeWithDepartment [id=1, name=Test1, remark=This is Test1, department=Department [id=1, name=Dep-A, remark=This is Department A]] ------------getEmployeeLazy------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_employee where id = ? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 Test1 [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_department where id = ? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 Department [id=1, name=Dep-A, remark=This is Department A] EmployeeWithDepartment [id=1, name=Test1, remark=This is Test1, department=Department [id=1, name=Dep-A, remark=This is Department A]]
resultMap: collection
OneToMany
Sample:DepartmentWithEmployeeMapper.xml
- Pojo: DepartmentWithEmployees
@Data public class DepartmentWithEmployees { private Integer id; private String name; private String remark; private List<Employee> employees; }
mapper: DepartmentWithEmployees.xml
<mapper namespace="com.cj.mybatis.mapper.DepartmentWithEmployeesMapper"> <!-- <resultMap type="com.cj.mybatis.entity.DepartmentWithEmployees" id="DeptWithEmpsMap"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="remark" property="remark"/> <collection property="employees" ofType="com.cj.mybatis.entity.Employee"> <id column="emp_id" property="id"/> <result column="emp_name" property="name"/> <result column="emp_remark" property="remark" /> <result column="emp_deptartment_id" property="departmentId" /> </collection> </resultMap> --> <resultMap type="com.cj.mybatis.entity.DepartmentWithEmployees" id="DeptWithEmpsMap" autoMapping="true"> <id column="id" property="id"/> <!-- must set! --> <collection property="employees" ofType="com.cj.mybatis.entity.Employee" columnPrefix="emp_" autoMapping="true"> </collection> </resultMap> <select id="getDepartment" resultMap="DeptWithEmpsMap"> select a.id id,a.name name,a.remark remark, b.id emp_id,b.name emp_name,b.remark emp_remark,b.department_id emp_department_id from pe_department a left join pe_employee b on a.id=b.department_id where a.id=#{id} </select> <resultMap type="com.cj.mybatis.entity.DepartmentWithEmployees" id="DeptWithEmpsLazyMap"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="remark" property="remark"/> <collection property="employees" select="com.cj.mybatis.mapper.EmployeeMapper.listEmployeesByDept" column="id" fetchType="lazy"> </collection> </resultMap> <select id="getDepartmentLazy" resultMap="DeptWithEmpsLazyMap"> select * from pe_department where id = #{id} </select> </mapper>
test
@Test public void testDepartmentWithEmployees() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ DepartmentWithEmployeesMapper deMapper = session.getMapper(DepartmentWithEmployeesMapper.class); System.out.println("------------getDepartment------------"); DepartmentWithEmployees dept = deMapper.getDepartment(1); System.out.println(dept); System.out.println("------------getDepartmentLazy------------"); dept = deMapper.getDepartmentLazy(1); System.out.println(dept.getName()); System.out.println(dept.getEmployees()); } }
- result
------------getDepartment------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select a.id id,a.name name,a.remark remark, b.id emp_id,b.name emp_name,b.remark emp_remark,b.department_id emp_department_id from pe_department a left join pe_employee b on a.id=b.department_id where a.id=? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 3 DepartmentWithEmployees [id=1, name=Dep-A, remark=This is Department A, employees=[Employee [id=1, name=Test1, remark=This is Test1, departmentId=1], Employee [id=2, name=Test2, remark=This is Test2, departmentId=1], Employee [id=3, name=Test3, remark=This is Test3, departmentId=1]]] ------------getDepartmentLazy------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_department where id = ? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 Dep-A [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_employee where department_id=? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 3 [Employee [id=1, name=Test1, remark=This is Test1, departmentId=1], Employee [id=2, name=Test2, remark=This is Test2, departmentId=1], Employee [id=3, name=Test3, remark=This is Test3, departmentId=1]]
resultMap: discriminator
- Pojo
@Data public class UserRole { private Integer id; private Integer userId; private String userType; private String roleName; }
mapper: UserRoleMapper.xml
<mapper namespace="com.cj.mybatis.mapper.UserRoleMapper"> <select id="listUserRoles" resultType="UserRole"> select * from pe_role </select> <resultMap id="userRoleMap" type="com.cj.mybatis.entity.UserRole"> <id column="id" property="id"/> <result column="role_name" property="roleName" /> <result column="user_id" property="userId" /> <result column="user_type" property="userType" /> <discriminator javaType="string" column="user_type"> <case value="Employee"> <result column="emp_name" property="name" /> <result column="emp_remark" property="remark" /> </case> <case value="Department"> <result column="dept_name" property="name" /> <result column="dept_remark" property="remark" /> </case> </discriminator> </resultMap> <select id="listUserRoleWithDetails" resultMap="userRoleMap"> select a.id id,a.role_name role_name,a.user_id user_id,a.user_type user_type, b.id emp_id,b.name emp_name,b.remark emp_remark,b.department_id emp_dept_id, c.id dept_id,c.name dept_name,c.remark dept_remark from pe_role a left join pe_employee b on a.user_id=b.id and a.user_type='Employee' left join pe_department c on a.user_id=c.id and a.user_type='Department'; </select> </mapper>
test
@Test public void testUserRole() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ UserRoleMapper urMapper = session.getMapper(UserRoleMapper.class); System.out.println("------------listUserRole------------"); List<UserRole> list = urMapper.listUserRoles(); System.out.println(list); System.out.println("------------listUserRoleWithDetails------------"); list = urMapper.listUserRoleWithDetails(); for(UserRole ur : list) { System.out.println(ur); } } }
- result
------------listUserRole------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_role [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: [QC] DEBUG BaseJdbcLogger.debug | <== Total: 6 [UserRole [id=1, userId=1, userType=Employee, roleName=manager, name=null, remark=null], UserRole [id=2, userId=2, userType=Employee, roleName=leader, name=null, remark=null], UserRole [id=3, userId=3, userType=Employee, roleName=temporary, name=null, remark=null], UserRole [id=4, userId=1, userType=Department, roleName=IT-01, name=null, remark=null], UserRole [id=5, userId=2, userType=Department, roleName=IT-02, name=null, remark=null], UserRole [id=6, userId=3, userType=Department, roleName=Finance-01, name=null, remark=null]] ------------listUserRoleWithDetails------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select a.id id,a.role_name role_name,a.user_id user_id,a.user_type user_type, b.id emp_id,b.name emp_name,b.remark emp_remark,b.department_id emp_dept_id, c.id dept_id,c.name dept_name,c.remark dept_remark from pe_role a left join pe_employee b on a.user_id=b.id and a.user_type='Employee' left join pe_department c on a.user_id=c.id and a.user_type='Department'; [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: [QC] DEBUG BaseJdbcLogger.debug | <== Total: 6 UserRole [id=1, userId=1, userType=Employee, roleName=manager, name=Test1, remark=This is Test1] UserRole [id=2, userId=2, userType=Employee, roleName=leader, name=Test2, remark=This is Test2] UserRole [id=3, userId=3, userType=Employee, roleName=temporary, name=Test3, remark=This is Test3] UserRole [id=4, userId=1, userType=Department, roleName=IT-01, name=Dep-A, remark=This is Department A] UserRole [id=5, userId=2, userType=Department, roleName=IT-02, name=Dep-B, remark=This is Department B] UserRole [id=6, userId=3, userType=Department, roleName=Finance-01, name=Dep-C, remark=This is Department C]
TypeHandler
在整个过程中,进行数据库类型和JavaBean类型的映射 (Table column => JavaBean property)
- DefaultParameterHandler typeHandler.setParameter(ps,i+1,value,jdbcType);
- DefaultResultSetHandler typeHandler.getResult(rs,column);
配置
Method 1: 全局配置(mybatis-config.xml) => 配置
<typeHandlers>
,<typeHandler>
<typeHandlers> <typeHandler handler="com.cj.mybatis.extend.MyEnumCodeTypeHandler" javaType="com.cj.mybatis.entity.EmployeeStatusEnum"/> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.cj.mybatis.entity.UserTypeEnum"/> </typeHandlers>
<!-- then in XxxMapper.xml : --> <insert id="insertOnEnumOrdinal" useGeneratedKeys="true" keyProperty="id" > insert into pe_role2(role_name,user_id,user_type) values( #{roleName}, #{userId}, #{userType} ) </insert> <select id="listOnEnumOrdinal" resultType="UserRole"> select * from pe_role2 </select>
Method 2: 局部配置(XxxMapper.xml) => 处理某个字段时设置 eg:
#{status,typeHandler=xxx}
,<resultMap> <result column="" property="" typeHandler="xxx">
<insert id="insertOnEnumOrdinal" useGeneratedKeys="true" keyProperty="id" parameterType="com.cj.mybatis.entity.UserRole"> must set parameterType! insert into pe_role2(role_name,user_id,user_type) values( #{roleName}, #{userId}, #{userType,typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler} ) </insert> <resultMap type="com.cj.mybatis.entity.UserRole" id="userRoleEnumOrdinalMap" autoMapping="true"> <id column="id" property="id"/> <result column="user_type" property="userType" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/> </resultMap> <select id="listOnEnumOrdinal" resultMap="userRoleEnumOrdinalMap"> select * from pe_role2 </select>
Method 3: 在自定义TypeHandler类上添加注解
@MappedTypes(value={Xxx.class})
/@MappedJdbcTypes(...)
注册 (eg:@MappedTypes({EmployeeStatusEnum.class})
)
处理Enum
处理枚举对象:
- 默认使用
EnumTypeHandler
,即操作的是枚举的名字 (=> select/insert: use enum name) - 可改变使用
EnumOrdinalTypeHandler
,即操作的是枚举的ordinal()
值(eg: 0,1,2,... => select/insert: use enum ordinal )
Sample: EnumTypeHandler
=> column: user_type(Employee,Department)
- enum
public enum UserTypeEnum { Employee,Department }
- entity
@Data public class UserRole { private Integer id; private Integer userId; private UserTypeEnum userType; private String roleName; }
mapper
<select id="listUserRoles" resultType="UserRole"> select * from pe_role </select> <insert id="insertUserRole" useGeneratedKeys="true" keyProperty="id"> insert into pe_role(role_name,user_id,user_type) values(#{roleName},#{userId},#{userType}) </insert>
test
@Test public void testInsert() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ UserRoleMapper userRoleMapper = session.getMapper(UserRoleMapper.class); UserRole userRole=new UserRole(); userRole.setRoleName("PE-01"); userRole.setUserId(4); userRole.setUserType(UserTypeEnum.Employee); Integer result = userRoleMapper.insertUserRole(userRole); System.out.println(result); System.out.println(userRole.getId()); session.commit(); } } /* Result Sample: [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: insert into pe_role(role_name,user_id,user_type) values(?,?,?) [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: PE-01(String), 4(Integer), Employee(String) [QC] DEBUG BaseJdbcLogger.debug | <== Updates: 1 1 8 */ @Test public void testList() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ UserRoleMapper urMapper = session.getMapper(UserRoleMapper.class); System.out.println("------------listUserRole------------"); List<UserRole> list = urMapper.listUserRoles(); for(UserRole ur : list) { System.out.println(ur); } } } /* Result Sample: ------------listUserRole------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_role [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: [QC] DEBUG BaseJdbcLogger.debug | <== Total: 7 UserRole [id=1, userId=1, userType=Employee, roleName=manager, name=null, remark=null] UserRole [id=2, userId=2, userType=Employee, roleName=leader, name=null, remark=null] UserRole [id=3, userId=3, userType=Employee, roleName=temporary, name=null, remark=null] UserRole [id=4, userId=1, userType=Department, roleName=IT-01, name=null, remark=null] UserRole [id=5, userId=2, userType=Department, roleName=IT-02, name=null, remark=null] UserRole [id=6, userId=3, userType=Department, roleName=Finance-01, name=null, remark=null] UserRole [id=8, userId=4, userType=Employee, roleName=PE-01, name=null, remark=null] */
Sample: EnumOrdinalTypeHandler
=> column: user_type (0,1)
- enum
public enum UserTypeEnum { Employee,Department }
- entity
@Data public class UserRole { private Integer id; private Integer userId; private UserTypeEnum userType; private String roleName; }
mapper
<!-- method 1 --> <insert id="insertOnEnumOrdinal" useGeneratedKeys="true" keyProperty="id" parameterType="com.cj.mybatis.entity.UserRole"> <!-- must set parameterType! --> insert into pe_role2(role_name,user_id,user_type) values( #{roleName}, #{userId}, #{userType,typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler} ) </insert> <resultMap type="com.cj.mybatis.entity.UserRole" id="userRoleEnumOrdinalMap" autoMapping="true"> <id column="id" property="id"/> <result column="user_type" property="userType" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/> </resultMap> <select id="listOnEnumOrdinal" resultMap="userRoleEnumOrdinalMap"> select * from pe_role2 </select> <!-- method 2 1. mybatis-config.xml <typeHandlers> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.cj.mybatis.entity.UserTypeEnum"/> </typeHandlers> 2. XxxMapper.xml <insert id="insertOnEnumOrdinal" useGeneratedKeys="true" keyProperty="id" > insert into pe_role2(role_name,user_id,user_type) values( #{roleName}, #{userId}, #{userType} ) </insert> <select id="listOnEnumOrdinal" resultType="UserRole"> select * from pe_role2 </select> -->
test
@Test public void testInsertOnEnumOrdinalTypeHandler() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ UserRoleMapper userRoleMapper = session.getMapper(UserRoleMapper.class); UserRole userRole=new UserRole(); userRole.setRoleName("PE-02"); userRole.setUserId(4); userRole.setUserType(UserTypeEnum.Department); Integer result = userRoleMapper.insertOnEnumOrdinal(userRole); System.out.println(result); System.out.println(userRole.getId()); session.commit(); } } /* [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: insert into pe_role2(role_name,user_id,user_type) values( ?, ?, ? ) [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: PE-02(String), 4(Integer), 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Updates: 1 1 4 */ @Test public void testListOnEnumOrinalTypeHandler() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ UserRoleMapper urMapper = session.getMapper(UserRoleMapper.class); System.out.println("------------listUserRole------------"); List<UserRole> list = urMapper.listOnEnumOrdinal(); for(UserRole ur : list) { System.out.println(ur); } } } /* ------------listUserRole------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_role2 [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: [QC] DEBUG BaseJdbcLogger.debug | <== Total: 3 UserRole [id=1, userId=1, userType=Department, roleName=PE-00, name=null, remark=null] UserRole [id=3, userId=4, userType=Department, roleName=PE-01, name=null, remark=null] UserRole [id=4, userId=4, userType=Department, roleName=PE-02, name=null, remark=null] */
处理Enum: 自定义TypeHandler
=> column: status(100,200,300)
步骤:
- 实现
TypeHandler
接口或者继承BaseTypeHandler
- 注册
TypeHandler
:- method1: 使用
@MappedTypes
定义处理的java类型(使用@MappedJdbcTypes
定义jdbcType类型) - method2: 在全局配置TypeHandler要处理的javaType
- method3: 在自定义结果集标签(
<resultMap>
)或者参数处理(@{xx,typeHandler=xxx}
)时声明使用自定义TypeHandler
进行处理
- method1: 使用
Sample:自定义TypeHandler处理枚举:在设置参数和取出结果集时自定义参数封装策略
enum
public enum EmployeeStatusEnum{ NORMAL(100,"正常"),CANCEL(200,"注销"),DELETE(300,"删除"); private Integer code; private String msg; private EmployeeStatusEnum(Integer code,String msg) { this.code=code; this.msg=msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }
- entity
@Data public class Employee { private Integer id; private String name; private String remark; private Integer departmentId; private EmployeeStatusEnum status; }
self defined TypeHandler: MyEnumCodeTypeHandler (实现
TypeHandler
接口,或者继承BaseTypeHandler
)public class MyEnumCodeTypeHandler extends BaseTypeHandler<EmployeeStatusEnum>{ private final Class<EmployeeStatusEnum> type; private final Map<Integer,EmployeeStatusEnum> enumMap; private final EmployeeStatusEnum[] enums; public MyEnumCodeTypeHandler(Class<EmployeeStatusEnum> type) { if (type == null) { throw new IllegalArgumentException("Type argument cannot be null"); } this.type = type; this.enums = type.getEnumConstants(); if (this.enums == null) { throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type."); } enumMap=new HashMap<Integer,EmployeeStatusEnum>(); for(EmployeeStatusEnum e:enums) { enumMap.put(e.getCode(), e); } } @Override public void setNonNullParameter(PreparedStatement ps, int i, EmployeeStatusEnum parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); } @Override public EmployeeStatusEnum getNullableResult(ResultSet rs, String columnName) throws SQLException { int code = rs.getInt(columnName); if (code == 0 && rs.wasNull()) { return null; } return toCodeEnum(code); } @Override public EmployeeStatusEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int code = rs.getInt(columnIndex); if (code == 0 && rs.wasNull()) { return null; } return toCodeEnum(code); } @Override public EmployeeStatusEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int code = cs.getInt(columnIndex); if (code == 0 && cs.wasNull()) { return null; } return toCodeEnum(code); } private EmployeeStatusEnum toCodeEnum(int code) { try { return this.enumMap.get(code); } catch (Exception ex) { throw new IllegalArgumentException("Cannot convert " + code + " to " + type.getSimpleName() + " by code value.", ex); } } }
mapper
<!-- Method 1 --> <insert id="insertEmployeeOnMyEnum" useGeneratedKeys="true" keyProperty="id" parameterType="Employee"> <!-- must set parameterType! --> insert into pe_employee(name,remark,department_id,status) values (#{name},#{remark},#{departmentId},#{status,typeHandler=com.cj.mybatis.extend.MyEnumCodeTypeHandler}) </insert> <resultMap type="Employee" id="employeesOnMyEnumMap" autoMapping="true"> <id column="id" property="id"/> <result column="status" property="status" typeHandler="com.cj.mybatis.extend.MyEnumCodeTypeHandler"/> </resultMap> <select id="listEmployeesOnMyEnum" resultMap="employeesOnMyEnumMap"> select * from pe_employee </select> <!-- Method 2 1. mybatis-config.xml <typeHandlers> <typeHandler handler="com.cj.mybatis.extend.MyEnumCodeTypeHandler" javaType="com.cj.mybatis.entity.EmployeeStatusEnum"/> </typeHandlers> 2. XxxMapper.xml <insert id="insertEmployeeOnMyEnum" useGeneratedKeys="true" keyProperty="id"> insert into pe_employee(name,remark,department_id,status) values (#{name},#{remark},#{departmentId},#{status}) </insert> <select id="listEmployeesOnMyEnum" resultType="Employee"> select * from pe_employee </select> -->
test
@Test public void testInsertOnMyEnumTypeHandler() throws Exception{ SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); System.out.println("------------insertEmployeeOnMyEnum------------"); Employee emp = new Employee(); emp.setName("QA-02"); emp.setRemark("This is Employee QA-02"); emp.setDepartmentId(15); emp.setStatus(EmployeeStatusEnum.CANCEL); Integer result = employeeMapper.insertEmployeeOnMyEnum(emp); System.out.println(result); System.out.println(emp.getId()); session.commit(); } } /* ------------insertEmployeeOnMyEnum------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: insert into pe_employee(name,remark,department_id,status) values (?,?,?,?) [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: QA-02(String), This is Employee QA-02(String), 15(Integer), 200(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Updates: 1 1 72 */ @Test public void testListOnMyEnumTypeHandler() throws Exception{ SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); System.out.println("------------listEmployees------------"); List<Employee> list = employeeMapper.listEmployeesOnMyEnum(); for(Employee ur : list) { System.out.println(ur); } } } /* ------------listEmployees------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_employee [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: [QC] DEBUG BaseJdbcLogger.debug | <== Total: 38 Employee [id=1, name=Test1, remark=This is Test1, depar Employee [id=68, name=Bat-19, remark=This is Employee Bat-19, departmentId=3, status=null] Employee [id=71, name=QA-01, remark=This is Employee QA-01, departmentId=15, status=NORMAL] Employee [id=72, name=QA-02, remark=This is Employee QA-02, departmentId=15, status=CANCEL] */
Dynamic Sql
- Doc
- 基于OGNL表达式简化SQL拼装
<if>
<choose>
(<when>
,<otherwise>
)<trim>
,<where>
,<set>
<foreach>
<bind>
: create a variable out of an OGNL expression and bind it to the context- configured variables for dynamic code:
_databaseId
,_parameter
_databaseId
: 代表当前数据库的别名(depending on database vendor, 配置databaseIdProvider
标签)_parameter
: 代表整个参数(单个参数:_parameter
即是这个参数;多个参数:参数会被封装成一个map,_parameter
即代表这个map)
- configured variables for dynamic code:
<sql>
可重用sql片段,<include>
引用定义的sql片段(里面可使用<property name="" value=""/>
自定义参数和值,传给sql片段)- 注:
<sql>
中取include
中property
定义的属性,只能使用${xxx}
,不能使用#{xxx}
- 注:
OGNL
Object Graph Navigation Language 对象图导航语言 (表达式语言,类似EL)
person.name
person.getName()
new com.cj.Persion('admin').name
调用静态方法
@java.lang.Math@PI
@java.util.UUID@randomUUID()
- 运算符:
+,-,*,/,%
- 逻辑运算符: in,not in,
>,>=
,<,<=
,==
,!=
- 注意:xml中特殊符号,如
<
,>
等需要使用转义字符
Sample: Dynamic Filter select
,if
<!--
Employee sample = new Employee();
sample.setName("Test1");
List<Employee> employees = employeeMapper.listEmployeesByExample(sample);
-->
<select id="listEmployeesByExample" resultType="Employee">
select * from pe_employee
where 1=1
<if test="id!=null"> and id=#{id} </if>
<if test="name!=null"> and name=#{name} </if>
<if test="remark!=null"> and remark=#{remark} </if>
</select>
Sample: Dynamic Update: update
,set
,if
<!--
Employee sample = new Employee();
sample.setId(9);
sample.setName("TestAB9");
sample.setRemark("This is Employee TestAB9");
Integer result= employeeMapper.updateEmployee(sample);
-->
<update id="updateEmployee">
update pe_employee
<set>
<if test="name!=null">name=#{name},</if>
<if test="remark!=null">remark=#{remark},</if>
<if test="departmentId!=null">department_id=#{departmentId}</if>
</set>
where id=#{id}
</update>
Sample: foreach
<!-- List<Employee> employees= employeeMapper.listEmployeesByDeptIds(Arrays.asList(1,3,5)); -->
<select id="listEmployeesByDeptIds" resultType="Employee">
select * from pe_employee where department_id in
<foreach collection="list" item="deptId" separator="," open="(" close=")">
#{deptId}
</foreach>
</select>
<!-- 批量插入:
Mysql:
method1: insert into T(...) values (),(),...
method2:
insert into T(...) values ()
insert into T(...) values ()
....
Oracle:
method1: insert into T(...) select ... from ...
method2:
begin
insert into T(...) values ();
insert into T(...) values ();
...
end
-->
<!-- public Integer insertMultipleEmployees(@param("emps")List<Employee> emps)-->
<insert id="insertMultipleEmployees">
insert into pe_employees(name,remark,department_id) values
<foreach collection="emps" item='emp' seperator=",">
(#{emp.name},#{emp.remark},#{emp.departmentId})
</foreach>
</insert>
<!--
注:这种方式数据库连接属性需加上 allowMultiQueries=true
( jdbc:mysql://localhost:3306/demo?allowMultiQueries=true )
-->
<!--
<insert id="insertMultipleEmployees">
<foreach collection="emps" item='emp' seperator=";">
insert into pe_employees(name,remark,department_id) values
(#{emp.name},#{emp.remark},#{emp.departmentId})
</foreach>
</insert>
-->
Sample: bind
<!-- List<Employee> employees= employeeMapper.listEmployeesByNameLike("Test"); -->
<select id="listEmployeesByNameLike" resultType="Employee">
<bind name="namePatten" value="'%' + name + '%'" />
select * from pe_employee where name like #{namePatten}
</select>
Sample: _parameter
,_databaseId
_parameter
<!-- 单参:listByName(String name)--> <select id="listByName" resultType="Employee"> select * fro pe_employees <if test="_parameter!=null"> where last_name = #{_paramter} </if> </select> <!-- 多参:ListBySample(Employee emp)--> <select id="ListBySample" resultType="Employee"> select * fro pe_employees <if test="_parameter!=null"> where last_name = #{_paramter.lastname} </if> </select>
_databaseId
<!-- mybatis-config.xml: <databaseIdProvider type="DB_VENDOR"> <property name="MySQL" value="mysql"/> <property name="Oracle" value="oracle"/> <property name="SQL Server" value="sqlserver"/> </databaseIdProvider> --> <!-- XxxMapper.xml--> <if test="_databaseId=='oracle'"> select seq_employee.nextval from dual; </if>
Sample: sql
,include
<update id="updateEmployee">
<!-- update pe_employee
<set>
<if test="name!=null">name=#{name},</if>
<if test="remark!=null">remark=#{remark},</if>
<if test="departmentId!=null">department_id=#{departmentId}</if>
</set>
where id=#{id} -->
update pe_employee
<set>
<include refid="empUpdateSet"/>
</set>
where id=#{id}
</update>
<sql id="empUpdateSet">
<if test="name!=null">name=#{name},</if>
<if test="remark!=null">remark=#{remark},</if>
<if test="departmentId!=null">department_id=#{departmentId}</if>
</sql>
Plugin
原理
- 拦截器接口
Interceptor
,插件即拦截器实现类(implements Interceptor
)package org.apache.ibatis.plugin; import java.util.Properties; public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP } }
- 插件原理:
- 四大对象每个创建的时候都有一个
interceptorChain.pluginAll(target)
- => 即插件介入:用拦截器们,通过动态代理机制一层层的包装目标对象,返回目标对象的代理对象,从而实现在目标对象执行目标方法之前进行拦截的效果(最终此代理对象可以拦截到四大对象的每一个执行)
- eg:
Configuration#newParameterHandler#interceptorChain.pluginAll(parameterHandler)
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
- 四大对象每个创建的时候都有一个
- 四大对象:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
- 插件会产生目标对象的代理对象,多个插件就会产生多层代理 => 创建动态代理的时候,按照插件配置顺序创建层层代理对象, 执行目标方法后,按照逆向顺序执行
自定义插件
- 编写
Interceptor
实现类 - 使用
@Intercepts({@Signature(type=Xxx.class,method="",args=[])})
注解完成插件签名 将写好的插件注册到全局配置文件中
<plugins>
-><plugin>
MyFirstPlugin.java
@Intercepts({ @Signature(type=StatementHandler.class,method="parameterize",args=Statement.class) }) public class MyFirstPlugin implements Interceptor{ @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("MyFirstPlugin#intercept:"+invocation.getMethod()); // test: change sql parameter value Object target = invocation.getTarget(); // 拿到target的元数据 MetaObject metaObject = SystemMetaObject.forObject(target); // StatementHandler -> ParameterHandler -> parameterObject Object value = metaObject.getValue("parameterHandler.parameterObject"); System.out.println("Get Sql Parameter Value:" + value); metaObject.setValue("parameterHandler.parameterObject", 11); Object proceed = invocation.proceed(); return proceed; } @Override public Object plugin(Object target) { // 使用当前Interceptor封装目标对象,返回一个新的代理对象 Object wrap = Plugin.wrap(target, this); System.out.println("MyFirstPlugin#plugin wrap:"+target); return wrap; } @Override public void setProperties(Properties properties) { // 获取Plugin注册时(mybatis-config.xml <plugins> <plugin>)设置的property System.out.println("MyFirstPlugin#setProperties:"+properties); } }
- mybatis-config.xml
<plugins> <plugin interceptor="com.cj.mybatis.extend.MyFirstPlugin"> <property name="testname" value="first-plugin"/> </plugin> </plugins>
- test
@Test public void testGet() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); Employee emp = employeeMapper.getEmployee(1); System.out.println(emp); } }
result
MyFirstPlugin#setProperties:{testname=first-plugin} MyFirstPlugin#plugin wrap:org.apache.ibatis.executor.CachingExecutor@569cfc36 MyFirstPlugin#plugin wrap:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@6eceb130 MyFirstPlugin#plugin wrap:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@5c072e3f MyFirstPlugin#plugin wrap:org.apache.ibatis.executor.statement.RoutingStatementHandler@954b04f [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_employee where id = ? MyFirstPlugin#intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException Get Sql Parameter Value:1 [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 11(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 Employee [id=11, name=Test-CC, remark=This is Employee CC, departmentId=3]
多个插件
- mybatis-config.xml
<plugins> <plugin interceptor="com.cj.mybatis.extend.MyFirstPlugin"> <property name="testname" value="first-plugin"/> </plugin> <plugin interceptor="com.cj.mybatis.extend.MySecondPlugin"> <property name="testname" value="second-plugin"/> </plugin> </plugins>
- result ( First wrap,Second wrap -> Target execute -> Second intercept,First intercept )
MyFirstPlugin#setProperties:{testname=first-plugin} MySecondPlugin#setProperties:{testname=second-plugin} MyFirstPlugin#plugin wrap:org.apache.ibatis.executor.CachingExecutor@569cfc36 MySecondPlugin#plugin wrap:org.apache.ibatis.executor.CachingExecutor@569cfc36 MyFirstPlugin#plugin wrap:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@6eceb130 MySecondPlugin#plugin wrap:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@6eceb130 MyFirstPlugin#plugin wrap:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@5c072e3f MySecondPlugin#plugin wrap:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@5c072e3f MyFirstPlugin#plugin wrap:org.apache.ibatis.executor.statement.RoutingStatementHandler@954b04f MySecondPlugin#plugin wrap:org.apache.ibatis.executor.statement.RoutingStatementHandler@954b04f [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_employee where id = ? MySecondPlugin#intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException MyFirstPlugin#intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 1(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 Employee [id=1, name=Test1, remark=This is Test1, departmentId=1]
第三方插件: PageHelper
- dependency
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency>
- mybatis-config.xml
<!-- typeAliases ... --> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="rowBoundsWithCount" value="true"/> </plugin> </plugins> <!-- environments ... -->
test:
PageHelper.startPage(pageNum,pageSize)
@Test public void testPage01() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); System.out.println("------------listEmployees------------"); Page<Object> page = PageHelper.startPage(1, 4); List<Employee> employees = employeeMapper.listEmployees(); for(Employee emp:employees) { System.out.println(emp); } System.out.println("Current Page:\t"+page.getPageNum()); System.out.println("Page Size:\t"+page.getPageSize()); System.out.println("Total Pages:\t"+page.getPages()); System.out.println("Total Records:\t"+page.getTotal()); } }
------------listEmployees------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: SELECT count(0) FROM pe_employee [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_employee LIMIT ? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 4(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 4 Employee [id=1, name=Test1, remark=This is Test1, departmentId=1] Employee [id=2, name=Test2, remark=This is Test2, departmentId=1] Employee [id=3, name=Test3, remark=This is Test3, departmentId=1] Employee [id=4, name=Dev4, remark=This is Dev4, departmentId=2] Current Page: 1 Page Size: 4 Total Pages: 4 Total Records: 15
test:
new PageInfo<>(List<T> list)
@Test public void testPage02() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); System.out.println("------------listEmployees------------"); PageHelper.startPage(1, 4); List<Employee> employees = employeeMapper.listEmployees(); PageInfo<Employee> pageInfo = new PageInfo<Employee>(employees); System.out.println("Current Page:\t"+pageInfo.getPageNum()); System.out.println("Page Size:\t"+pageInfo.getPageSize()); System.out.println("Total Pages:\t"+pageInfo.getPages()); System.out.println("Total Records:\t"+pageInfo.getTotal()); System.out.print("List Nav Pages:\t"); for(int i : pageInfo.getNavigatepageNums()) { System.out.print(i+" "); } System.out.println(); System.out.println("List Records:"); List<Employee> results=pageInfo.getList(); for(Employee emp : results) { System.out.println(emp); } System.out.println(results); } }
------------listEmployees------------ [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: SELECT count(0) FROM pe_employee [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: [QC] DEBUG BaseJdbcLogger.debug | <== Total: 1 [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: select * from pe_employee LIMIT ? [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 4(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 4 Current Page: 1 Page Size: 4 Total Pages: 4 Total Records: 15 List Nav Pages: 1 2 3 4 List Records: Employee [id=1, name=Test1, remark=This is Test1, departmentId=1] Employee [id=2, name=Test2, remark=This is Test2, departmentId=1] Employee [id=3, name=Test3, remark=This is Test3, departmentId=1] Employee [id=4, name=Dev4, remark=This is Dev4, departmentId=2] Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=15, pages=4, reasonable=false, pageSizeZero=false}[Employee [id=1, name=Test1, remark=This is Test1, departmentId=1], Employee [id=2, name=Test2, remark=This is Test2, departmentId=1], Employee [id=3, name=Test3, remark=This is Test3, departmentId=1], Employee [id=4, name=Dev4, remark=This is Dev4, departmentId=2]]
Cache
一级缓存
- 本地缓存,
sqlSession
级别(默认开启,一个Map) - 与数据库同一次session查询到的数据会放在本地缓存中,以后获取相同数据,会直接从缓存中取而不用再去DB查询
- 一级缓存失效情况(会再向DB发出查询)
- 不同sqlSession
- 同一sqlSeesion
- 查询条件不同(一级缓存中还没有这个数据)
- 两次查询中间执行了CUD操作(因为这次CUD可鞥会对当前数据有影响)
- 手动清除了一级缓存(缓存清空)
二级缓存
- 全局缓存,
namespace
级别(不同namespace
查出的数据会放在自己对应的缓存中,需手动开启和配置) - 一个会话中查询数据放入一级缓存,会话提交或关闭后,一级缓存中数据保存到二级缓存中,新的会话查询信息,可参照二级缓存
- 使用步骤:
- 开启全局二级缓存配置
<setting name="cacheEnabled" value="true"/>
- mapper中配置使用二级缓存
<cache/>
eviction
缓存回收策略 LRU(最近最少使用的,默认),FIFO(先进先出),SOFT(软引用),WEAK(弱引用)flushInterval
缓存刷新间隔 (毫秒,多长时间清空一次,默认不清空)readOnly
是否只读(true则认为只读,会将数据在缓存中的引用交给用户,不安全,速度快;false则会利用序列化反序列技术克隆一份给用户,安全,速度慢)size
缓存存放多少元素type
指定自定义缓存的全类名(实现Cache接口的自定义类全名)
- POJO实现序列化接口
- 开启全局二级缓存配置
缓存相关配置
- 全局二级缓存开关
<setting name="cacheEnabled" value="true/false" />
- mapper中
<select>
配置是否使用二级缓存<select useCache="true/false" ... >
- 参照缓存:若想在命名空间中共享相同的缓存配置和实例,可使用
<cache-ref namespace="...xxxMapper" />
引用另一个缓存 - mapper中sql标签上配置sql执行后是否清除缓存(包括一级二级)
- 增删改标签上的
flushCache
默认为true - 查询标签上的
flushCache
默认为false
- 增删改标签上的
- 清除当前session的一级缓存:程序执行
sqlSession.clearCache()
localCacheScope
本地缓存作用域 SESSION(一级缓存)/STATEMENT(禁用一级缓存)- 注:
- 在某一个作用域(一级缓存session/二级缓存namespace)进行了CUD后,默认该作用域下的所有select缓存将被clear
- Mybatis提供了缓存接口
Cache
,供扩展实现自定义的二级缓存
第三方缓存整合
EhCache
- 纯Java进程内缓存框架,是Hibernate中默认的CacheProvider
- 使用步骤:
- 导入ehcache包,整合包(mybatis-echcache),日志包
- 编写ehcache.xml配置文件
- mapper中配置
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
标签
与SpringBoot整合
dependencies
<dependencies>
<!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<!-- for @ConfigurationProperties : optional ! -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
druid
Github: druid | druid-spring-boot-starter
application.yml
server: port: 8080 servlet: context-path: /demo spring: datasource: druid: url: jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowMultiQueries=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # type: com.alibaba.druid.pool.DruidDataSource initial-size: 8 min-idle: 1 max-active: 20 max-wait: 60000 time-between-eviction-runsMillis: 60000 min-evictable-idle-timeMillis: 300000 validation-query: select 'x' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: false max-open-prepared-statements: 20 max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall use-global-data-source-stat: true connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
Configuration
@Configuration public class DruidConfig { //doc: https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter // inject configed druid properties @ConfigurationProperties(prefix = "spring.datasource.druid") @Bean public DataSource druidDataSource(){ return new DruidDataSource(); } //配置Druid的监控 //1、配置一个管理后台的Servlet @Bean public ServletRegistrationBean<StatViewServlet> statViewServlet(){ ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<StatViewServlet>(new StatViewServlet(), "/druid/*"); Map<String,String> initParams = new HashMap<>(); initParams.put("loginUsername","admin"); initParams.put("loginPassword","123456"); //initParams.put("allow","");//默认就是允许所有访问 //initParams.put("deny","192.168.15.21"); bean.setInitParameters(initParams); return bean; } //2、配置一个web监控的filter @Bean public FilterRegistrationBean<WebStatFilter> webStatFilter(){ FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<WebStatFilter>(); bean.setFilter(new WebStatFilter()); Map<String,String> initParams = new HashMap<>(); initParams.put("exclusions","*.js,*.css,/druid/*"); bean.setInitParameters(initParams); //bean.setUrlPatterns(Arrays.asList("/*")); return bean; } }
- start application,then visit: http://localhost:8080/demo/druid to login
MyBatis(单数据源)
application.yml
server: port: 8080 servlet: context-path: /demo spring: datasource: druid: url: jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowMultiQueries=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # type: com.alibaba.druid.pool.DruidDataSource initial-size: 8 min-idle: 1 max-active: 20 max-wait: 60000 time-between-eviction-runsMillis: 60000 min-evictable-idle-timeMillis: 300000 validation-query: select 'x' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: false max-open-prepared-statements: 20 max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall use-global-data-source-stat: true connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 mybatis: mapper-locations: classpath:mybatis/mapper/*Mapper.xml config-location: classpath:mybatis-config.xml
Druid Configuration: DruidConfig.java
@Configuration public class DruidConfig { //https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter @ConfigurationProperties(prefix = "spring.datasource.druid") @Bean public DataSource druidDataSource(){ return new DruidDataSource(); } // 配置Druid的监控 & 监控的Filter // ... }
Global Config: mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- <setting name="logImpl" value="STDOUT_LOGGING" /> --> </settings> <typeAliases> <package name="com.cj.mybatis.entity"/> </typeAliases> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="rowBoundsWithCount" value="true"/> </plugin> </plugins> </configuration>
XxxMapper.xml: mybatis/mapper/DepartmentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cj.mybatis.mapper.DepartmentMapper"> <select id="listAll" resultType="Department"> select * from pe_department </select> </mapper>
Dao Interface: com/cj/mybatis/mapper/DepartmentMapper.java
public interface DepartmentMapper { public List<Department> listAll(); }
Entity: com/cj/mybatis/entity/Department.java
@Data public class Department { private Integer id; private String name; private String remark; }
Service: com/cj/mybatis/service/DepartmentService.java
@Service public class DepartmentService { @Autowired private DepartmentMapper departmentMapper; public List<Department> listAll(){ return this.departmentMapper.listAll(); } }
Controller
@RestController public class DepartmentController { @Autowired DepartmentService departmentService; @GetMapping("/departments") public Object listAll() { return ResponseUtil.ok(departmentService.listAll()); } }
util/ResponseUtil.java
public class ResponseUtil { private boolean success; private Object data; public ResponseUtil(boolean success, Object data) { this.success = success; this.data = data; } public static ResponseUtil result(boolean success,Object data) { return new ResponseUtil(success, data); } public static ResponseUtil ok(Object data) { return new ResponseUtil(true,data); } public static ResponseUtil fail(Object data) { return new ResponseUtil(false,data); } // Getter & Setter ... // .... }
MyBatis(多数据源)
application.yml
server: port: 8080 servlet: context-path: /demo spring: datasource: first: url: jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowMultiQueries=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # type: com.alibaba.druid.pool.DruidDataSource initial-size: 8 min-idle: 1 max-active: 20 max-wait: 60000 time-between-eviction-runsMillis: 60000 min-evictable-idle-timeMillis: 300000 validation-query: select 'x' FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: false max-open-prepared-statements: 20 max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall use-global-data-source-stat: true connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 second: url: jdbc:mysql://localhost:3306/demo2?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowMultiQueries=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # type: com.alibaba.druid.pool.DruidDataSource initial-size: 8 min-idle: 1 max-active: 20 max-wait: 60000 time-between-eviction-runsMillis: 60000 min-evictable-idle-timeMillis: 300000 validation-query: select 'x' FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: false max-open-prepared-statements: 20 max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall use-global-data-source-stat: true connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # config sqlSessionFactory bean,this would be useless! # mybatis: # mapper-locations: classpath:mybatis/mapper/*Mapper.xml # config-location: classpath:mybatis-config.xml
Configuration
@Configuration @MapperScan(basePackages="com.cj.mybatis.dao.first",sqlSessionTemplateRef="firstSqlSessionTemplate") public class FirstDruidConfig { // @Primary @Bean(name="firstDataSource") @ConfigurationProperties(prefix="spring.datasource.first") public DataSource firstDataSource() { return DruidDataSourceBuilder.create().build(); } // @Primary @Bean(name="firstSqlSessionFactory") public SqlSessionFactory firstSqlSessionFactory(@Qualifier("firstDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); //factoryBean.setTypeAliasesPackage("com.cj.mybatis.entity"); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mybatis/mapper/first/*Mapper.xml")); factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver() .getResource("classpath:mybatis-config.xml")); return factoryBean.getObject(); } // @Primary @Bean(name="firstTransactionManager") public DataSourceTransactionManager firstDataSourceTransactionManager(@Qualifier("firstDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } // @Primary @Bean(name="firstSqlSessionTemplate") public SqlSessionTemplate firstSqlSessionTemplate(@Qualifier("firstSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
@Configuration @MapperScan(basePackages="com.cj.mybatis.dao.second",sqlSessionTemplateRef="secondSqlSessionTemplate") public class SecondDruidConfig { @Bean(name="secondDataSource") @ConfigurationProperties(prefix="spring.datasource.second") public DataSource secondDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name="secondSqlSessionFactory") public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); //factoryBean.setTypeAliasesPackage("com.cj.mybatis.entity"); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mybatis/mapper/second/*Mapper.xml")); factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver() .getResource("classpath:mybatis-config.xml")); return factoryBean.getObject(); } @Bean(name="secondTransactionManager") public DataSourceTransactionManager secondDataSourceTransactionManager(@Qualifier("secondDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name="secondSqlSessionTemplate") public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
// Druid Monitor 控制台 @Configuration public class DruidConfig { @Bean public ServletRegistrationBean<StatViewServlet> statViewServlet(){ ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<StatViewServlet>(new StatViewServlet(), "/druid/*"); Map<String,String> initParams = new HashMap<>(); initParams.put("loginUsername","admin"); initParams.put("loginPassword","123456"); bean.setInitParameters(initParams); return bean; } @Bean public FilterRegistrationBean<WebStatFilter> webStatFilter(){ FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<WebStatFilter>(); bean.setFilter(new WebStatFilter()); Map<String,String> initParams = new HashMap<>(); initParams.put("exclusions","*.js,*.css,/druid/*"); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList("/*")); return bean; } }
目录划分:
- resources:
mybatis/mapper/first/*Mapper.xml
mybatis/mapper/second/*Mapper.xml
- java:
com.cj.mybatis.dao.first: *Mapper.java
com.cj.mybatis.dao.second: *Mapper.java
- resources:
Service: 注意事务时指定具体事务名
@Transactional(xxx)
@Service public class EmployeeService { @Autowired private EmployeeMapper employeeMapper; @Transactional("firstTransactionManager") public Integer updateEmployee(Employee emp) { return employeeMapper.updateEmployee(emp); } }
@Service public class DepartmentService { @Autowired private DepartmentMapper departmentMapper; @Transactional("secondTransactionManager") public List<Integer> insertMultipleDepartments(int count) { List<Integer> results=new ArrayList<>(); for(int i=0;i<count;i++) { Department dept = new Department(null,"TDept-"+i,"This is TDept-"+i); results.add(departmentMapper.insert(dept)); } return results; } }
高级
运行原理
SqlSessionFactoryBuilder#build
=> 创建SqlSessionFactory
(默认实现类:DefaultSqlSessionFactory
)Configuration
对象,封装了所有配置文件(全局,sql映射配置文件)的详细信息- 注:会解析XxxMapper.xml中增删改查标签的每一个标签每一个属性,封装成一个
MappedStatement
(一个MappedStatement就代表一个CRUD标签的详细信息)
SqlSessionFactory#openSession
=> 创建一次连接会话SqlSession
对象 (默认实现类:DefaultSqlSession
)- 里面包括
Configuration
对象和根据全局配置文件中的defaultExecutorType
创建出对应的Executor
- 里面包括
sqlSession#getMapper
=> 返回mapper接口对应的代理对象MapperProxy
(implements XxxMapper
,里面包括SqlSession
对象)使用返回的代理对象
MapperProxy
调用接口,执行配置的CRUD方法- 调用
SqlSession
的CRUD(Executor
) 2) 创建StatementHandler
对象(同时也会创建出ParameterHandler
,ResultSetHandler
) 3) 调用StatementHandler
预编译参数及设置参数值,使用ParameterHandler
处理预编译参数 4) 调用StatementHandler
的CRUD方法 => JDBC:Statement,PreparedStatement,... 5) 使用ResultSetHandler
封装结果集
- 调用
总结(主要对象):
Executor
(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed) 执行sqlParameterHandler
(getParameterObject,setParameters) 处理预编译参数ResultSetHandler
(handleResultSet,handleOutputParameters) 处理结果集StatemnetHandler
(prepare,parameterize,batch,update,query) 处理Sql语句预编译,设置参数等TypeHandler
用来在整个过程中,处理Table column => JavaBean property 映射DefaultParameterHandler typeHandler.setParameter(ps,i+1,value,jdbcType)
;DefaultResultSetHandler typeHandler.getResult(rs,column);
- 注: 四大对象(
Executor
,ParameterHandler
,ResultSetHandler
,StatementHandler
)每个创建的时候都会执行interceptorChain.pluginAll(parameterHandler)
操作,执行插件拦截器
存储过程
mysql procedure:
-- 创建 delimiter $ create procedure get_page_result( IN p_pageNum INTEGER, IN p_pageSize INTEGER, OUT total Integer ) begin declare offsetNum int default 0; if p_pageNum is null then set p_pageNum=0; end if; if p_pageSize is null then set p_pageSize=0; end if; set offsetNum = (p_pageNum-1) * p_pageSize; select count(1) into total from pe_employee ; select * from pe_employee limit offsetNum,p_pageSize; end $ -- 测试 set @total=0; call get_page_result(2,3,@total); select @total; -- 删除 drop procedure get_page_result;
XxxMapper.xml
<!-- public List<Employee> getPageResult(MyPageInfo page); @Data public class MyPageInfo { private Integer pageNum; private Integer pageSize; private Integer total; } --> <select id="getPageResult" statementType="CALLABLE" resultType="Employee"> {call get_page_result( #{pageNum,mode=IN}, #{pageSize,mode=IN}, #{total,mode=OUT,jdbcType=INTEGER} )} </select>
test
@Test public void testProcedure() throws IOException { SqlSessionFactory factory = getSqlSessionFactory(); try(SqlSession session=factory.openSession()){ EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); MyPageInfo page = new MyPageInfo(); page.setPageNum(13); page.setPageSize(3); page.setTotal(0); List<Employee> list = employeeMapper.getPageResult(page); for(Employee emp:list) { System.out.println(emp); } System.out.println(page.getTotal()); } } /* [QC] DEBUG BaseJdbcLogger.debug | ==> Preparing: {call get_page_result( ?, ?, ? )} [QC] DEBUG BaseJdbcLogger.debug | ==> Parameters: 13(Integer), 3(Integer) [QC] DEBUG BaseJdbcLogger.debug | <== Total: 3 [QC] DEBUG BaseJdbcLogger.debug | <== Updates: 0 Employee [id=71, name=QA-01, remark=This is Employee QA-01, departmentId=15, status=NORMAL] Employee [id=72, name=QA-02, remark=This is Employee QA-02, departmentId=15, status=CANCEL] Employee [id=73, name=QA-02, remark=This is Employee QA-02, departmentId=15, status=CANCEL] 43 */
批量操作
- 非批量:预编译sql & 设置参数 => 执行10000次
SqlSession session=factory.openSession()
- 批量:预编译sql & 设置参数 10000次 => 执行1次
SqlSession session=factory.openSession(ExecutorType.BATCH)
- 注: springboot中可以注入
sqlSessionFactory.openSession(ExecutorType.BATCH)
来获取可以执行批量操作的sqlSession
Sample:
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class BathExecutorTest {
@Autowired
private SqlSessionFactory sqlSessionFactory;
/*
EmployeeMapper.xml:
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="id">
insert into pe_employee(name,remark,department_id) values (#{name},#{remark},#{departmentId})
</insert>
*/
@Test
public void testBatchExecutor() {
try(SqlSession session=sqlSessionFactory.openSession(ExecutorType.BATCH)){
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
for(int i=0;i<5;i++) {
Employee emp = new Employee(null,"Test-"+i,"This is Test-"+i,3);
Integer result = employeeMapper.insertEmployee(emp);
System.out.println(result+","+emp.getId());
}
System.out.println("Done");
}
}
}
/*
==> Preparing: insert into pe_employee(name,remark,department_id) values (?,?,?)
==> Parameters: Test-0(String), This is Test-0(String), 3(Integer)
-2147482646,null
==> Parameters: Test-1(String), This is Test-1(String), 3(Integer)
-2147482646,null
==> Parameters: Test-2(String), This is Test-2(String), 3(Integer)
-2147482646,null
==> Parameters: Test-3(String), This is Test-3(String), 3(Integer)
-2147482646,null
==> Parameters: Test-4(String), This is Test-4(String), 3(Integer)
-2147482646,null
*/
More
显示sql语句
- method 1: mybatis-config.xml
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="STDOUT_LOGGING" /> <!-- 控制台显示sql语句--> </settings>
method 2: log4j.properties
log4j.rootLogger=error,Stdout # 控制台显示sql语句 log4j.logger.com.cj.mybatis=debug # 控制台 log4j.appender.Stdout=org.apache.log4j.ConsoleAppender log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout log4j.appender.Stdout.layout.ConversionPattern=[QC] %p %C{1}.%M | %m%n log4j.appender.Stdout.Target=System.err
与springboot结合: application.yml
logging: level: root: warn com.cj.mybatis.mapper: debug
logback日志
application.yml
# 默认就使用logback-spring.xml,可不配置 logging: config: classpath:logback-spring.xml
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <conversionRule conversionWord="mycolor" converterClass="com.cj.mybatis.util.LogColor" /> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern> %mycolor(%msg%n) </Pattern> </layout> </appender> <!-- 显示sql语句 --> <logger name="com.cj.mybatis" level="DEBUG" /> <root level="info"> <appender-ref ref="STDOUT" /> </root> </configuration>
彩色输出(自定义实现类:com.cj.mybatis.util.LogColor.java)
package com.cj.mybatis.util; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.pattern.color.ANSIConstants; import ch.qos.logback.core.pattern.color.ForegroundCompositeConverterBase; public class LogColor extends ForegroundCompositeConverterBase<ILoggingEvent>{ @Override protected String getForegroundColorCode(ILoggingEvent event) { Level level = event.getLevel(); switch(level.toInt()) { case Level.ERROR_INT: return ANSIConstants.RED_FG; case Level.WARN_INT: return ANSIConstants.YELLOW_FG; case Level.INFO_INT: return ANSIConstants.BLUE_FG; case Level.DEBUG_INT: return ANSIConstants.GREEN_FG; case Level.TRACE_INT: return ANSIConstants.DEFAULT_FG; default: return ANSIConstants.DEFAULT_FG; } } }
Logger使用示例:
@RestControllerAdvice public class ExceptionControllerAdvice { private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionControllerAdvice.class); @ExceptionHandler(Throwable.class) public ResponseUtil onException(Throwable ex,HttpServletRequest request){ LOGGER.error("url: {}, msg: {}", request.getRequestURL(), ex.getMessage()); return ResponseUtil.fail("get exception:"+ex.getMessage()); } }