MyBatis:基本使用,动态SQL,逆向工程MBG
概述
一个ORM(持久层)框架
- JDBC(JavaDataBase Connectivity)是 Java 数据库连接——用 Java 操作数据库
- Mybatis 针对 JDBC 中重复操作做了封装,扩展并优化部分功能
- ORM:对象关系映射(Object Relational Mapping),通过描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中——ORM中间件能在任何一个应用的业务逻辑层和数据库层之间充当桥梁;ORM会牺牲程序的执行效率,因为ORM是面向对象的,并且应用于多层系统
与其他ORM的对比:
- JDBC:
- SQL 夹杂在代码中,修改SQL需要重新编译 Java 类
- 维护不易
- 无连接池
- Hibernate:
- 内部自动生产的 SQL 不容易做特殊优化
- 反射操作多
- MyBatis:
- SQL 和 Java 代码分开
- JDBC:
架构图:
- MyBatis xml配置文件:
- mybatis-config.xml是MyBatis的主配置文件,是全局的,保存数据库的连接信息、运行环境等
- mapper.xml 是 sql 映射文件,记录对数据库进行增删改查的sql语句,映射文件需要在主配置文件mybatis-config.xml里显式的加载(见后文的xml配置)
- SqlSessionFactory会话工厂:
- SqlSessionFactory创建各种会话,实际上是一个接口,接口中定义了openSession的不同的加载方法
- 一般用单例模式来管理SqlSessionFactory,便于重复使用
- 工厂模式:如果创建某一个对象,使用的过程基本固定,可以把创建这个对象的相关代码封装到一个“工厂类”中,以后使用工厂类“生产”需要的对象
- SqlSession会话:面向用户的,即一个用户对应一个会话,定义数据库的操作方法,类似于JDBC中的Connection对象
- 每个线程都应有自己的SqlSession实例。SqlSession的实例不能共享使用,因为不是线程安全的
- 打开了一个SqlSession,则在使用完毕后需要关闭它。通常sqlsession.close()关闭操作,放到finally{}块里,以确保每次都能正确关闭
- executor执行器:MyBatis底层自定义了Execuor执行器接口操作数据库(执行SQL语句),包括2个执行器:基本执行器、缓存执行器
- MappedStatement映射器:
- MyBatis定义的一个底层封装对象,包装了xml配置信息、sql映射信息等
- 在Mapper.xml里,一个sql语句对应一个MappedStatement映射器对象,sql的id也是MappedStatement映射器的id——接收输入映射(SQL语句中的参数),返回输出映射(即将SQL查询的结果映射成相应的结果)
- MyBatis xml配置文件:
范例
基本过程
数据库:建立库
testMyBatis
,建立表Emp
——表名和后面的类一致;表的列和类的属性一致环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28<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驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>配置日志(
src/main/resources/log4j.xml
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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>配置文件(
src/main/resources目录
):习惯上命名为mybatis-config.xml,用于配置连接数据库的环境和MyBatis的全局配置信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<configuration>
<!--设置连接数据库的环境-->
<!--配置环境,可配置多个环境(比如:develop开发、test测试-->
<environments default="development">
<environment id="development">
<!--1.1.配置事务管理方式:JDBC/MANAGED
JDBC:将事务交给JDBC管理(推荐)
MANAGED:自己管理事务-->
<transactionManager type="JDBC"/>
<!--1.2.配置数据源,即连接池 JNDI/POOLED/UNPOOLED
JNDI:已过时
POOLED:使用连接池(推荐)
UNPOOLED:不使用连接池
-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/testMyBatis?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<!--如果mapper文件有多个,可以通过多个mapper标签导入-->
<mappers>
<mapper resource="mappers/EmpMapper.xml"/>
</mappers>
</configuration>- 对应数据库为testMyBatis,因此url中为testMyBatis
- 由于数据库建立时,使用了utf-8编码,因此需要characterEncoding=utf8
- mappers/EmpMapper.xml位于
src/main/resources
下
建立Emp类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38package com.testMyBatis.tables;
public class Emp {
private Integer id;
private String name;
private String job;
private Double salary;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public String getJob() {
return job;
}
public Double getSalary() {
return salary;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setJob(String job) {
this.job = job;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public String toString() {
return "Emp [id=" + id + ", name=" + name + ", job=" + job + ", salary=" + salary + "]";
}
}建立mapper接口:
1
2
3
4
5
6
7
8
9
10package com.testMyBatis.mappers;
import java.util.List;
import com.testMyBatis.tables.Emp;
public interface EmpMapper {
// 查询所有的用户信息
List<Emp> findAll();
}在
src/main/resources
下创建mappers/EmpMapper.xml映射文件命名规则:
- 表所对应的实体类的类名+Mapper.xml
- 一个映射文件对应一个实体类,对应一张表的操作
- MyBatis中可以面向接口操作数据,要保证:
- mapper接口的全类名和映射文件的命名空间(namespace)保持一致,此时namespace应当也为com.test
- mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
映射文件内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--不同Mapper文件的namespace值应该保证唯一,在程序中通过[ namespace + id ]定位到要执行哪一条SQL语句-->
<mapper namespace="com.testMyBatis.mappers.EmpMapper">
<select id="findAll" resultType="com.testMyBatis.tables.Emp">
select * from Emp
</select>
</mapper>- namespace不能重复
- id不能重复
- select必须设置属性设置resultType或resultMap,表明实体类和数据库表的映射关系
- resultType属性:从这条SQL语句返回所期望类的完全限定名称(包名+类名)——如果返回一个集合,则为一个集合中元素的类型;用于属性名和表中字段名一致的情况
- resultMap属性:复杂对象结构(例如多表关联查询等),用于一对多或多对一或字段名和属性名不一致的情况
测试:通过代理模式创建EmpMapper接口的代理实现类对象,根据EmpMapper的全类名匹配映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40package com.testMyBatis;
import com.testMyBatis.mappers.EmpMapper;
import com.testMyBatis.tables.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class test {
public void findAll() throws IOException {
// 读取mybatis的核心配置文件(mybatis-config.xml)
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 通过配置信息获取一个SqlSessionFactory工厂对象
SqlSessionFactory fac = new SqlSessionFactoryBuilder().build(in);
// 创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
SqlSession session = fac.openSession();
// List<Emp> list = session.selectList("com.testMyBatis.mappers.EmpMapper.findAll");
// for (Emp e : list) {
// System.out.println(e);
//}
// 通过代理模式创建EmpMapper接口的代理实现类对象
EmpMapper empMapper = session.getMapper(EmpMapper.class);
// 调用EmpMapper接口中的方法,就可以根据EmpMapper的全类名匹配映射文件,
// 通过调用的方法名匹配映射文件中的SQL标签,并执行标签中的SQL语句
List<Emp> result = empMapper.findAll();
for (Emp e : result) {
System.out.println(e);
}
}
}1
2
3
4
5
6
7
8DEBUG 09-03 13:47:53,839 ==> Preparing: select * from Emp (BaseJdbcLogger.java:137)
DEBUG 09-03 13:47:53,873 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 09-03 13:47:53,919 <== Total: 5 (BaseJdbcLogger.java:137)
Emp [id=1, name=a, job=程序员, salary=3300.0]
Emp [id=2, name=b, job=程序员, salary=2800.0]
Emp [id=3, name=c, job=程序员, salary=2700.0]
Emp [id=4, name=d, job=总监, salary=4200.0]
Emp [id=5, name=e, job=程序员, salary=3000.0]综上:
- 类对应数据库中的表格
- mapper接口对应xml映射文件的命名空间,接口的函数名对应映射文件中的id,代理模式创建代理类对象,对象调用接口中的函数来执行映射文件中的sql语句
- xml映射文件的名字在mybatis-config.xml中定义
- mybatis-config.xml在初始化一个SqlSessionFactory工厂对象时传入
其他固定的CRUD
增删改的标签不用指定resultType,因为返回值都是int,接口函数可以为void
映射文件中添加增删改标签
1
2
3
4
5
6
7
8
9
10
11<update id="insert">
insert into emp value(null, 'f', '保安', 6000)
</update>
<update id="update">
update emp set job='门卫', salary=20000 where name='f'
</update>
<update id="delete">
delete from emp where name='f'
</update>mapper接口声明增删改函数
1
2
3
4
5
6public interface EmpMapper {
...
void insert();
void update();
void delete();
}
占位符(带参数的CRUD)
#{}
:本质是占位符赋值- 为SQL语句中的参数值进行占位
- 大部分情况下都是使用#{}
- 为字符串或者日期类型的值进行占位时,在参数值传过来替换占位符的同时,会进行转义处理(在字符串或日期类型的值的两边加上单引号)
${}
:本质是字符串拼接- 将传过来的SQL片段直接拼接在该占位符所在的位置,不会进行任何的转义处理,因此可能需要添加单引号
- 使用该占位符时,即使只有一个占位符,也需要将参数先封装再传递
- 例如:
- 动态传递查询的列,若使用
select #{columns} from emp
,会被转译为select 'id,name,job' from emp
,而使用select ${columns} from emp
则直接拼接为select id,name,job from emp
- 动态传递查询的列,若使用
不同类型的参数
mapper接口中方法的参数为一个:可以使用
${}
和#{}
以任意的名称获取参数的值mapper接口:
1
2
3
4public interface EmpMapper {
...
List<Emp> getDataByName(String name);
}映射文件:注意
#{}
和${}
的差别1
2
3
4<select id="getDataByName" resultType="com.testMyBatis.tables.Emp">
<!--select * from emp where username = #{name}-->
select * from emp where name='${name}'
</select>
mapper接口中方法的参数为多个:
MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为key、以参数为value(
#{}
,以param0,param1…为key、以参数为value(${}
)mapper接口:
1
List<Emp> getDataByNameSalary(String name, double salary);
映射文件:
1
2
3
4<select id="getDataByNameSalary" resultType="com.testMyBatis.tables.Emp">
<!--select * from emp where name = #{arg0} and salary = #{arg1}-->
select * from emp where name = '${param1}' and salary = ${param2}
</select>
可以手动创建map集合,通过
${}
和#{}
访问map集合的key获得对应的value——key即为#{}
中的占位字符串mapper接口:
1
List<Emp> getDataByNameSalaryMap(Map<String, Object> map);
映射文件:
1
2
3<select id="getDataByNameSalaryMap" resultType="com.testMyBatis.tables.Emp">
select * from emp where name=#{name} and salary=#{salary}
</select>测试:
1
2
3
4Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("salary", 3300.0);
List<Emp> result = empMapper.getDataByNameSalaryMap(map);
使用实体类作为参数:访问实体类对象中的属性名获取属性值
mapper接口:
1
void insertUser(Emp emp);
映射文件:
1
2
3<insert id="insertUser">
insert into emp values(null, #{name}, #{job}, #{salary})
</insert>
使用@Param标识参数:通过@Param注解标识mapper接口中的方法参数,这些参数放入map集合,以@Param注解的值为key、参数为value(
#{}
)mapper接口:
1
List<Emp> getDataByNameSalaryParam(double salary) String name, ;
映射文件:
1
2
3<select id="getDataByNameSalaryParam" resultType="com.testMyBatis.tables.Emp">
select * from emp where name=#{name} and salary=#{salary}
</select>
MyBatis的查询
上面给出了查询一个实体类对象、查询一个list集合实体对象的示例——一个对象在表格里是一行记录
MyBatis对于Java常用的类型都设置了类型别名,例如:java.lang.Integer–>int|integer、int–>_int|_integer、Map–>map、List–>list
要求返回单个数据:
mapper接口:
1
int getCount();
映射文件:
1
2
3<select id="getCount" resultType="_integer">
select count(id) from emp
</select>
要求返回一条数据,格式为map:输出的map中,key为表的字段(String类型),value为该数据在相应字段下的值(Object类型)
mapper接口:
1
Map<String, Object> getRecordToMap(int id) ;
映射文件:
1
2
3<select id="getRecordToMap" resultType="map">
select * from emp where id=#{id}
</select>
要求返回多条数据,格式为map:
方法一:返回map列表
mapper接口:
1
List<Map<String, Object>> getAllRecordToMap1();
映射文件:
1
2
3<select id="getAllRecordToMap1" resultType="map">
select * from emp
</select>
方法二:返回map<map>:需要通过@MapKey注解设置map集合的key,value是每条数据所对应的map。下面的例子,key为每条数据的id
mapper接口:
1
2
Map<String, Object> getAllRecordToMap2();映射文件:同上
1
2
3
4
5
6
7{
1={name=a, id=1, job=q, salary=3300.0},
2={name=b, id=2, job=q, salary=2800.0},
3={name=c, id=3, job=q, salary=2700.0},
4={name=d, id=4, job=q, salary=4200.0},
5={name=e, id=5, job=q, salary=3000.0}
}
特殊SQL
模糊查询
1
2
3
4
5<select id="testMohu" resultType="com.testMyBatis.tables.Emp">
<!--select * from t_user where username like '%${mohu}%'-->
<!--select * from t_user where username like concat('%',#{mohu},'%')-->
select * from emp where name like "%"#{mohu}"%"
</select>- SQL模糊查询:
SELECT 字段 FROM 表 WHERE 某字段 Like 条件
- %:表示任意0个或多个字符。可匹配任意类型和长度的字符,例如
SELECT * FROM user WHERE u_name LIKE '%三%'
——SELECT * FROM user WHERE u_name LIKE '%三%' AND u_name LIKE '%猫%'
又有“三”又有“猫”、SELECT * FROM user WHERE u_name LIKE '%三%猫%'
“三”在“猫”前面 - _:表示任意单个字符。匹配单个任意字符,例如
SELECT * FROM user WHERE u_name LIKE '_三_'
,三个字且中间为“三” - [ ]:表示括号内所列字符中的一个(类似正则表达式),例如
SELECT * FROM user WHERE u_name LIKE '老[1-9]'
(找出“老1”等)、SELECT * FROM user WHERE u_name LIKE '[张李王]三'
(找出“张三”、“李三”等)、SELECT * FROM user WHERE u_name LIKE '老[c-f]'
(找出“老c”等) - [^ ] :表示不在括号所列之内的单个字符
- %:表示任意0个或多个字符。可匹配任意类型和长度的字符,例如
- SQL模糊查询:
批量删除:foreach(见后文动态SQL)
动态设置表名
1
2
3<select id="getAllUser" resultType="com.testMyBatis.tables.Emp">
select * from ${tableName}
</select>
resultMap
- 上面的例子中,表中字段名和表对应类的属性名是一致的。若字段名和实体类中的属性名不一致,则通过resultMap设置自定义映射
- resultMap:设置自定义映射
- 属性:
- id:表示自定义映射的唯一标识
- type:查询的数据要映射的实体类的类型
- 子标签:
- id:设置主键的映射关系
- result:设置普通字段的映射关系
- association:设置多对一的映射关系
- collection:设置一对多的映射关系
- 属性:
- property:设置映射关系中实体类中的属性名
- column:设置映射关系中表中的字段名
- 属性:
1 | <resultMap id="唯一标识" type="映射的entity对象的绝对路径"> |
一对一映射
1 | <resultMap id="empMap" type="com.testMyBatis.tables.Emp"> |
- 在MyBatis核心配置文件设置一个全局配置信息mapUnderscoreToCamelCase,在查询表中数据时自动将_类型的字段名转换为驼峰——
user_name
转换为userName
多对一映射
例如,将两个表关联起来的查询语句,查询某个员工信息以及员工所对应的部门信息——其中Emp类的属性有dept对象,dept对象具有属性did、dname
1
2
3
4
5
6
7
8
9
10
11
12
13
14<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<association property="dept" javaType="Dept">
<id column="did" property="did"></id>
<result column="dname" property="dname"></result>
</association>
</resultMap>
<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.eid = #{eid}
</select>
一对多映射
例如,根据部门id查询新的部门和部门中的员工信息——此时部门类具有属性
List<Emp> emps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<resultMap id="deptEmpMap" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<!--
ofType:设置collection标签所处理的集合属性中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="ename" column="ename"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>
<!--Dept getDeptEmpByDid(@Param("did") int did);-->
<select id="getDeptEmpByDid" resultMap="deptEmpMap">
select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did = emp.did where dept.did = #{did}
</select>分步查询(略)
动态SQL
根据特定条件动态拼装SQL语句
sql片段:记录一段公共sql片段,在使用的地方通过include标签进行引入
1
2
3
4
5
6<sql id="empColumns">
name, job, salary
</sql>
<select id="getEmpByConditionTrim" resultType="com.testMyBatis.tables.Emp">
select <include refid="empColumns"></include> from emp
</select>If
可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会拼接到sql语句
表达式中的name为Emp类的属性。
property != null
适用于任何类型的字段判断属性值是否为空;property != ''
仅适用于String类型字段判断是否为空字符串mapper:
1
List<Emp> getEmpByConditionOne(Emp emp);
映射文件:
1
2
3
4
5
6
7
8
9
10
11
12<select id="getEmpByConditionIf" resultType="com.testMyBatis.tables.Emp">
select * from emp where 1=1
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="job != null and job != ''">
and job = #{job}
</if>
<if test="salary != null and salary != 0">
and salary = #{salary}
</if>
</select>
Where
where和if一般结合使用:
- 若where标签中的if条件都不满足,则where标签没有任何功能
- 若where标签中的if条件满足,则自动添加where关键字,并将条件最前方多余的 and去掉
mapper:
1
List<Emp> getEmpByConditionWhere(Emp emp);
映射文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14<select id="getEmpByConditionWhere" resultType="com.testMyBatis.tables.Emp">
select * from emp
<where>
<if test="name != null and name != ''">
name = #{name}
</if>
<if test="job != null and job != ''">
and job = #{job}
</if>
<if test="salary != null and salary != 0">
and salary = #{salary}
</if>
</where>
</select>
choose、when、otherwise:相当于if…else if..else
mapper:
1
List<Emp> getEmpByChoose(Emp emp);
映射文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<select id="getEmpByChoose" resultType="com.testMyBatis.tables.Emp">
select * from emp
<where>
<choose>
<when test="name != null and name != ''">
name = #{name}
</when>
<when test="job != null and job != ''">
job = #{job}
</when>
<when test="salary != null and salary != 0.0">
salary = #{salary}
</when>
<otherwise>
1 = 1
</otherwise>
</choose>
</where>
</select>
Trim:
<trim prefix="", suffix="", prefixOverrides="", suffixOverrides=""> </trim>
prefix:给sql增加前缀
suffix:给sql增加后缀
prefixOverrides:去掉sql前面多余的关键字或者字符
suffixOverrides:去掉sql前面多余的关键字或者字符
映射文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<sql id="empColumns">
name, job, salary
</sql>
<select id="getEmpByConditionTrim" resultType="com.testMyBatis.tables.Emp">
select <include refid="empColumns"></include> from emp
<trim prefix="where" suffixOverrides="and|or">
<if test="name != null and name != ''">
name = #{name} and
</if>
<if test="job != null and job != ''">
job = #{job} or
</if>
<if test="salary != null and salary != 0.0">
salary = #{salary}
</if>
</trim>
</select>- sql语句为
from emp where ...
,如果job满足条件,salary不满足条件,则将最后的or删除形成最终的sql语句
- sql语句为
Foreach
输入参数为一个数组,或者集合
separator:设置循环体之间的分隔符
item:表示集合或数组中的每一个数据
collection:设置要循环的数组或集合
例如:
批量删除(通过数组)
1
int deleteMoreByArray(int[] ids) ;
1
2
3
4
5
6<delete id="deleteMoreByArray">
delete from emp where
<foreach collection="ids" item="id" separator="or">
id = #{id}
</foreach>
</delete>效果:
delete from emp where id = ? or id = ? or id = ?
注意,这里collection属性等于Param注解提供的值,通过它访问参数——建议除了实体对象和map以外,其他参数输入都用这个方法
或者:
1
2
3
4
5
6<delete id="deleteMoreByArray">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>效果:
delete from emp where id in (?, ?, ?)
批量添加(通过集合)
1
2
3
4
5
6<insert id="insertMoreEmp">
insert into emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.name},#{emp.job},#{emp.salary})
</foreach>
</insert
缓存
缓存只对查询有用
一级缓存
- 默认开启
- SqlSession级别,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据会从缓存直接获取,不会重新访问数据库
- 一级缓存失效:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession
- 查询条件不同(即执行不同的SQL查询,新的mapper接口实例执行同样的查询不会失效)
- 两次查询期间执行了一次增删改操作
- 两次查询期间手动清空了缓存
二级缓存
范围更大
SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取(SqlSessionFactory级别高于SqlSession)
二级缓存开启的条件:
在核心配置文件中,设置全局配置属性cacheEnabled=”true”,默认为true
在映射文件中设置标签
<cache/>
1
2<mapper namespace="com.testMyBatis.mappers.EmpMapper">
<cache flushInterval=""/>二级缓存必须在SqlSession关闭或提交之后有效——即,两个session,第一个session查询后关闭,第二个session再次查询
查询的数据所转换的实体类类型必须实现序列化的接口
失效:两次查询之间执行增删改
在mapper配置文件中添加的cache标签可以设置一些属性:
- eviction属性:缓存回收策略
- LRU(Least Recently Used):移除最长时间不被使用的对象
- FIFO(First in First out)
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
- 默认LRU
- flushInterval属性:刷新间隔,单位毫秒(默认不设置,仅仅调用语句时刷新)
- size属性:引用数目,正整数,代表缓存最多可以存储多少个对象
- readOnly属性:只读,true/false
- true:只读缓存;给所有调用者返回缓存对象的相同实例。这些对象不能被修改
- false:读写缓存;通过序列化返回缓存对象的拷贝,此时修改这个对象不会改变缓存中的内容。慢一些,但安全
- 默认false
- eviction属性:缓存回收策略
缓存查询顺序:
- 先查询二级缓存——可能会有其他程序已经查出的数据
- 二级缓存没有命中,再查询一级缓存
- 一级缓存也没有命中,查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存
三级缓存EHCache
略
逆向工程(MyBatis Generator)
正向工程:先创建Java实体类,由框架根据实体类生成数据库表——Hibernate支持正向工程,目前MyBatis不能独立支持(以上的所有操作,都需要数据库中预先建立一个表结构)
逆向工程:先创建数据库表,由框架根据数据库反向生成Java实体类、Mapper接口、Mapper映射文件——代码生成器
此时可以通过任意字段,以任意的条件进行CRUD(QBC风格,即条件都是定义好的,只需要调用对应的方法,就可以生成标准的条件),但逆向工程生成的是单个表的CRUD,多表连接、 存储过程等这些复杂sql的定义需要手工编写
创建一个新工程MyBatis_MBG
依赖和插件(pom.xml):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>MyBatis_MBG</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!--打包结果为jar-->
<packaging>jar</packaging>
<!-- 依赖MyBatis核心包 -->
<dependencies>
<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驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</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>5.1.8</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>mybatis_config.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<configuration>
<properties resource="jdbc.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>
<package name="mappers"/>
</mappers>
</configuration>jdbc.properties:
1
2
3
4com.mysql.jdbc.Driver =
jdbc:mysql://localhost:3306/testMyBatis?characterEncoding=utf8 =
root =
123456 =逆向工程的配置文件,必须为
generatorConfig.xml
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD
MyBatis3: 生成带条件的CRUD
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/testMyBatis?characterEncoding=utf8"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.testMyBatis.tables" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="mappers" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.testMyBatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="emp" domainObjectName="Emp"/>
<!--<table tableName="t_dept" domainObjectName="Dept"/>-->
</context>
</generatorConfiguration>- enableSubPackages:为true,则每个“.”都是一层package
- trimStrings:如果表格中的字段前后有空格,则将空格去掉,得到类的属性名
运行:
运行效果:
* ByExample:条件查询 * Selective:选择性运行(例如,如果某个对象的一个属性为null,则插入数据时不会将相应字段填入记录)
分页插件
依赖:
1
2
3
4
5<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>mybatis-config.xml中配置插件
1
2
3
4<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>使用:
查询之前
PageHelper.startPage(int pageNum, int pageSize)
开启分页功能- pageNum:当前页的页码
- pageSize:每页显示的条数
获取list集合之后,使用
PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)
获取分页相关数据分页相关数据:
1
2
3
4
5
6
7PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30, pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}- pageNum:当前页的页码
- pageSize:每页显示的条数
- size:当前页显示的真实条数
- total:总记录数
- pages:总页数
- prePage:上一页的页码
- nextPage:下一页的页码
- isFirstPage/isLastPage:是否为第一页/最后一页
- hasPreviousPage/hasNextPage:是否存在上一页/下一页
- navigatePages:导航分页的页码数