快速介绍
Mybatis是一个半自动的ORM(object-relation mapping,对象关系映射)框架,之所以是半自动,是因为其将sql编写的灵活性增加了,允许sql定制化开发,避免了JDBC中的大部分代码编写,通过xml或者注解的方式可以方便地将java对象同数据库记录进行映射,只需要在程序中操作java对象,就可以操作数据库相关记录,为持久化提供了良好的解决方案。
快速使用
- 引入Mybatis依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
- 编写全局配置文件(globalConfig.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>
<properties resource="db.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="StudentMapper.xml" />
</mappers>
</configuration>
- 编写数据库配置文件(db.properties)
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/mybatis
db.username=root
db.password=123456
- 编写映射文件(StudentMapper.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="develop">
<select id="findById"
parameterType="int"
resultType="site.zhaoyangjue.entity.Student">
select * from student where id = #{id}
</select>
<select id="findByName"
parameterType="java.lang.String"
resultType="site.zhaoyangjue.entity.Student">
select * from student where name like '%${value}%'
</select>
</mapper>
<!--
如果基于Mapper形式的方式开发,则此文件中的namespace与Mapper接口的类路径要相同
id要与接口名称相同,
parameterType要与接口方法入参类型相同
resultType类型要与接口方法返回值类型相同
-->
- 编写java对象实体类(Student)
public class Student {
private int id;
private String name;
private Date birthday;
private String sex;
private String address;
// 此处省略getter/setter、toString方法
}
- 编写dao相关类(StudentDao,StudentDaoImpl)
public interface StudentDao {
Student findById(int id) throws Exception;
List<Student> findByName(String name) throws Exception;
void insertStudent(Student student) throws Exception;
}
public class StudentDaoImpl implements StudentDao {
private SqlSessionFactory sqlSessionFactory;
public StudentDaoImpl(SqlSessionFactory sqlSessionFactory){
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public Student findById(int id) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
Student student = null;
try{
student = sqlSession.selectOne("develop.findById", id);
} finally {
sqlSession.close();
}
return student;
}
@Override
public List<Student> findByName(String name) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<Student> studentList = null;
try{
studentList = sqlSession.selectList("develop.findByName", name);
} finally {
sqlSession.close();
}
return studentList;
}
@Override
public void insertStudent(Student student) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
sqlSession.insert("develop.insertStudent", student);
sqlSession.commit();
} finally {
sqlSession.close();
}
}
}
// 基于xml方式的开发方式,则只需要根据session获取Mapper操作对象即可:
// StudentMapper studentMapper = session.getMapper(StudentMapper.class);
// 此处的studentMapper是一个接口。
- 组织查询操作
public class MybatisTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws Exception{
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream resourceAsStream = Resources.getResourceAsStream("globalConfig.xml");
sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
}
@Test
public void testFindById() throws Exception {
StudentDaoImpl studentDao = new StudentDaoImpl(sqlSessionFactory);
Student studentById = studentDao.findById(2);
System.out.println(studentById);
}
@Test
public void testFindByName() throws Exception {
StudentDaoImpl studentDao = new StudentDaoImpl(sqlSessionFactory);
List<Student> studentList = studentDao.findByName("张三");
System.out.println(studentList);
}
@Test
public void testInsertStudent() throws Exception {
StudentDaoImpl studentDao = new StudentDaoImpl(sqlSessionFactory);
Student student = new Student();
student.setId(5);
student.setName("小七");
student.setAddress("上海市");
student.setBirthday(new Date());
student.setSex("男");
studentDao.insertStudent(student);
}
}
快速总结
#{}和${}区别
在组织sql时都用到了#{}和${},他们的区别在于:
#{} | ${} |
---|---|
等同于jdbc中的PreparedStatement(?) | 等同于jdbc中的Statement(+) |
输入映射时,会对参数进行类型解析 | 输入映射时,将参数原样输出 |
对简单类型的输入映射时,参数名称可以任意 | 对简单类型的输入映射时,参数名称必须是value |
不存在sql注入风险 | 存在sql注入风险 |
是通过反射获取数据(StaticSqlSource) | 以OGNL表达式的形式随着嵌套关系而发生层级变化(DynamicSqlSource) |
开发方式
基于xml方式开发和基于注解进行开发(@Select(sql语句),@Insert(sql语句)的形式,标注在接口方法声明上,一般比较少会用到)
在基于xml方式开发时,需要注意以下几点:
- Mapper接口的类路径要与Mapper.xml文件中的namespace相同;
- Mapper接口方法名称要与Mapper.xml中定义的每一个statement的id相同;
- Mapper接口方法入参类型要与Mapper.xml中定义的每一个sql的parameterType类型相同;
- Mapper接口方法返回值类型要与Mapper.xml定义的每个sql的resultType类型相同。
全局配置文件
配置项 | 含义说明 | 备注 |
---|---|---|
properties | 定义属性 | 通过resource属性引入properties文件,定义property字标签定义属性和属性值,加载属性的顺序是加载元素体内的属性,然后加载元素中的resource或url属性,如果出现同名属性,会发生覆盖 |
settings | 定义全局配置参数 | |
typeAliases | 定义别名 | 简化类型名称编写,默认是支持的,alias指定别名,type指定类型路径 |
typeHandlers | 类型处理器 | 将java类型转换为JDBC类型并转换为数据库类型 |
objectFactory | 对象工厂 | |
plugins | 定义插件 | |
environments | 环境相关的属性集合 | |
environment | 环境子属性对象 | |
transactionManager | 事务管理 | |
dataSource | 定义数据源 | |
mappers | 映射器 | resource指定相对路径,url指定绝对路径,class指定mapper接口类路径 |
上述程序在StudentMapper.xml中出现的parameterType用于定义输入参数的java类型;resultType用于定义查询结果的映射类型。
输入输出映射
parameterType用于定义输入参数的java类型,可以定义的java类型有简单类型、普通对象类型、Map和List类型。
resultType用于定义输出参数的java类型,可以定义的java类型有简单类型、普通对象类型、Map类型。在使用时,查询的列名需要同普通对象类型中的属性名称保持一致。
resultMap用于将列名和属性名作为一个对应关系,此时查询列名和普通对象类型中的参数可以不一致。
<!-- type属性:指定映射的对象类型 id:resultMap的唯一标识 --><resultMap type="student" id="userListResultMap"> <!-- id标签:映射查询结果的唯一列(主键列)column:查询sql列名 property:映射对象的属性名 --> <id column="id" property="id"/> <!-- result标签:映射除id之外的其他字段 --> <result column="name" property="stuName"/> <result column="birthday" property="birthday"/></resultMap>
快速升级
关联查询
关联查询分为一对一、一对多、多对多,其中多对多可以看做是双向的一对多或者多对一,所以重点关注一对一和一对多即可。
- 一对一
在一对一的场景下,可以使用resultMap,也可以使用resultType。
新增一对一的Mapper.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="site.zhaoyangjue.dao.SidDao"> <resultMap id="stuInfoWithStudentResultMap" type="site.zhaoyangjue.entity.StuInfo"> <id column="id" property="id"></id> <result column="info" property="info"></result> <result column="s_id" property="sId"></result> <!--association定义一对一映射,property定义映射属性对象,javaType定义映射属性对象类型--> <association property="student" javaType="site.zhaoyangjue.entity.Student"> <id column="id" property="id"></id> <result column="name" property="name"></result> <result column="address" property="address"></result> </association> </resultMap> <select id="findStuInfoWithStudent" resultMap="stuInfoWithStudentResultMap"> select a.id,a.info,a.s_id,b.id,b.id,b.address from stu_info a join student b on a.s_id=b.id </select></mapper>
- 一对多
在一对多场景下,只能使用resultMap,因为查询结果是多条记录。
<?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="site.zhaoyangjue.dao.ClassInfoDao"> <resultMap id="queryStudentsWithClassMap" type="site.zhaoyangjue.entity.ClassInfo"> <id column="id" property="id"></id> <result column="name" property="cname"></result> <!--使用collection定义一对多映射,property定义多的那一方的信息,ofType用于指定多的那一方的对象类型--> <collection property="studentList" ofType="site.zhaoyangjue.entity.Student"> <id column="id" property="id"></id> <result column="name" property="name"></result> <result column="address" property="address"></result> </collection> </resultMap> <select id="queryStudentsWithClass" resultMap="queryStudentsWithClassMap"> select a.id,a.name,a.address,b.name,b.id from student a join class_info b on a.c_id=b.id </select></mapper>
延迟加载
延迟加载属于关联查询场景下的优化操作,在进行查询时,按照设置的延迟规则对关联对象(对主对象没有作用)进行延迟查询,一定程度上可以减少数据库压力。根据执行时机,可以分为以下几种:
- 直接加载:主加载对象执行完select之后,立刻执行关联对象的select.
<settings> <setting name="lazyLoadingEnabled" value="false"/></settings>
- 侵入式延迟:只有当要访问主加载对象的某属性时,才会执行关联对象的select
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="true"/></settings>
- 深度延迟:只有真正访问关联对象的详情时,才会执行关联对象的select。该模式在关联对象对应表比较多的时候会产生N+1问题,即查询关联对象对应的数据表多次。
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/></settings>
动态SQL
if标签
<if test="condition"> branch</if>
where标签
where标签会处理后面的第一个and,从而避免1=1查询带来的性能问题
<where>where-info</where>
sql标签
可以将sql片段进行封装,无需多次编写,只需要在使用时引用即可
<sql id="sql片段名称"> sql片段</sql>
include标签
可以将sql片段引用到其他的sql中
<include refid="namespace.sql片段名称"></include>
foreach标签
<foreach collection="集合的名称" item="集合中的元素变量" open="拼接在遍历sql的前面" close="拼接在遍历sql后面" separator="遍历sql之间的分隔符号"> #{参与遍历的字段}</foreach>
缓存
- 一级缓存
一级缓存是SqlSession级别的缓存,其内部采用hashMap来存储缓存数据,不用的sqlSession之间的缓存相互独立。mybatis默认是开启的。一级缓存中数据的生命周期是同sqlSession一致的,随着SqlSession的消亡而消亡。
- 二级缓存
二级缓存是mapper(namespace)级别的,二级缓存可以简单理解为基于mapper级别的一级缓存,只不过mapper已经是mybatis中最大的缓存了。mybatis默认是没有开启二级缓存的。
<settings> <setting name="cacheEnabled" value="true"></setting></settings><!--单个mapper映射文件,默认使用的是perpetualCache--><cache></cache>
某一个statement想要禁用二级缓存,可以在对应的查询标签中设置useCache=false
二级缓存的刷新(flushCache),默认对于select语句为false,insert、update、delete语句为true。在设置二级缓存的刷新时要将二级缓存打开,同时要设置刷新间隔属性(flushInterval,单位为毫秒)。