MyBatis是优秀的持久层框架,它避免了几乎所有的JDBC代码以及设置参数获取结果集的过程。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和java的实体类映射成数据库中的记录。
持久化:持久化是将程序数据在持久状态和瞬时状态转化的机制,即把数据保存到可永久保存的存储设备上(如磁盘)。主要应用是将内存中的数据存储数据库或其他磁盘文件中。
持久层:持久层就是完成持久化操作的代码块,对应于java web的dao层(数据访问对象层),在企业中,持久化的实现往往通过各种关系型数据库来完成。这里层的概念表明在系统架构中持久化操作是一个相对独立的逻辑层面,对于其他层面来说,持久层的主要作用就是专注于数据持久化逻辑的实现。
mybatis执行基本流程
mybatis程序构建流程
1、数据库准备
2、导入mybatis相关jar包
3、编写mybatis核心配置文件
4、编写mybatis工具类
5、创建实体类
6、编写Mapper接口
7、编写Mapper.xml配置文件
8、进行代码测试
预备工作
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis? useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<configuration>
<properties resource="db.properties">
<property name="password" value="062433"/>
properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
<typeAliases>
<typeAlias type="com.lee.pojo.User" alias="User"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="root"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper class="com.lee.dao.UserMapper"/>
mappers>
configuration>
用于获取sqlSession连接
package com.lee.util;
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 java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
/*工具类返回一个sqlSession对象,用来操作sql语句*/
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//获取配置资源
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSession(){
//自动开启事务
return sqlSessionFactory.openSession(true);
}
}
注:使用Maven创建项目时,由于静态资源过滤问题,可能会导致配置无法导出的情况,需要在Maven中进行如下配置。
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
build>
MyBatis的配置文件包含9大标签:
下面按重要性逐一介绍
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="root"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
该元素用于配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,该标签上必须指定其中一个为默认运行(通过default指定)。
每套环境中的子元素节点
可选择不同的事务管理器:
该元素通过选择不同的数据源类型,用来配置不同的数据源实现(
type="[UNPOOLED|POOLED|JNDI]")
),有以下三种:
- UNPOOLED:每次被请求时打开和关闭连接。(简单的数据库连接)
- POOLED:利用池的概念将JDBC连接对象组织起来,适用于并发请求响应。(类似于数据库连接池)
- JNDI:用于能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
主要用于定位到定义映射SQL语句文件(*Mapper.xml)的资源路径,直观的来说就是通知MyBatis到何处去寻找映射文件。有以下几种方式供选择:
<mappers>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
mappers>
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
mappers>
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
mappers>
<mappers>
<package name="org.mybatis.builder"/>
mappers>
注:在使用注解开发时,可以不使用mapper标签进行显示映射,关于注解开发的内容将在后面讨论。
对应的mapper映射文件:
<mapper namespace="com.lee.mapper.TestMapper">
mapper>
namespac(命名空间),作用如下:
- namespace和子元素的id联合保证唯一 , 区别不同的mapper
- 绑定DAO接口
— namespace的命名必须跟某个接口同名
— 接口中的方法与映射文件中sql语句id应该一一对应- namespace命名规则 : 包名+类名
该元素用于配置数据库的相关属性,既可以在外部通过java属性文件进行配置,也可以通过子元素property来配置。
<properties resource="db.properties">
<property name="password" value="062433"/>
properties>
该元素用于为java类型设置一个简单的类型别名,仅用于xml配置,存在的意义仅在于用来减少类完全限定名的冗余。
<typeAliases>
<typeAlias type="com.Lee.pojo.User" alias="User"/>
typeAliases>
也可以指定包名,MyBatis 会在包名下面搜索需要的 Java Bean
<typeAliases>
<package name="com.kuang.pojo"/>
typeAliases>
此时如果包下的Java Bean没有注解,那么会使用其首字母小写的非限定类名作为别名;若包含注解,则注解值为其别名。
@Alias("user")
public class User {
...
}
此外,Mybatis还对常见的java内建类型提供了默认的别名,可参考【Mybatis文档】进行查看
该元素主要用于设置MyBatis的相关功能,例如懒加载、开启日志、开启缓存等。一个完整的设置如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
settings>
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。该元素通过声明自己的类型处理器来处理特殊的类型。
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
使用插件时需要实现Interceptor接口,并指定想要拦截的方法签名,最后需要在mybatis配置文件中进行注册。
注:尽量少使用该元素,因为可能会对mybatis的行为产生极大的影响。
数据库厂商标识,目的是为了支持多厂商特性。
在搭建好mybatis环境后,就可以通过编写mapper接口和配置文件对数据进行基本的操作—增、删、改、查。
注:
- mapper配置文件中的namespace中的名称必须对应Mapper接口的完整包名。
- 增删改操作需要提交事务
引入一个简单的例子作为操作对象。首先,我们假设UserMapper接口文件放置于项目路径的com.lee.dao包下,其中用到的实体类User定义于com.lee.pojo包下,文件内容为:
package com.lee.pojo;
//采用lomnok注释
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User{
private int id;
private String name;
private String pwd;
}
package com.lee.dao;
import com.lee.pojo.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//查询所有用户
List<User> selectList();
//查询指定用户
User selectUserById(int id);
//实现模糊查询
User selectLike(int id);
//多参量查询
User selectUserByNP2(Map<String,Object> map);
//增加用户
int addUser(User user);
//删除用户
int deleteUser(int id);
//更新用户信息
int updateUser(User user);
//分页获取用户信息
List<User> getUserListByLimit(Map<String, Integer> map);
}
select标签是mybatis中最常用的标签之一,其包含了许多用于配置SQL语句的属性,比较常用的有
定义UserMapper.xml实现sql操作
<mapper namespace="com.lee.dao.UserMapper">
<select id="getUserList" resultType="User">
select * from mybatis.user
select>
<select id="selectUserById" parameterType="int" resultType="User">
select * from mybatis.user where id=#{id}
select>
mapper>
上面两种方式比较简单,不过多赘述。对于模糊查询来说,有两种方式可选:
//部分java代码
string wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like #{value}
select>
//部分java代码
string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like "%"#{value}"%"
select>
如果是针对某些(多个)参数进行查询,可通过Map实现
//部分java代码
Map<String, Object> map = new HashMap<String, Object>();
map.put("username","Lee");
map.put("pwd","123456");
User user = mapper.selectUserByNP2(map);
<select id="selectUserByNP2" parameterType="map" resultType="com.kuang.pojo.User">
select * from user where name = #{username} and pwd = #{pwd}
select>
与select类似
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
insert>
//测试代码
@Test
public void addTest(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.addUser(new User(4, "min", "789798"));
//工具类中获取的SqlSession已默认自动提交事务
session.close();
}
<update id="updateUser" parameterType="com.kuang.pojo.User">
update user set name=#{name},pwd=#{pwd} where id = #{id}
update>
@Test
public void updateTest(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(3);
user.setName("weilai");
mapper.updateUser(user);
session.close();
}
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
delete>
@Test
public void deleteTest(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.deleteUser(4);
session.close();
}
注解开发可用于比较简单的SQL操作(不包括mybatis映射操作)实现,可以通过简单的注释代码来避免mapper配置文件的编写。用法比较简单,仅需在mapper接口中方法上添加对应的SQL实现注解即可。
需要注意的是,由于无需编写配置文件在mybatis配置中就无法对配置文件进行映射,因此通过绑定接口文件实现映射。
示例如下:
public interface UserMapper{
//根据id查询用户
@Select("select * from user where id = #{id}")
User selectUserById(@Param("id") int id);
//添加一个用户
@Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
int addUser(User user);
//修改一个用户
@Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
int updateUser(User user);
//根据id删除用
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id")int id);
}
方法的调用方式与之前相同,实际上,这种方式可简单理解为将原有操作从XML驱动转变为java api驱动实现,其本质并未改变。
SQL语句中分页由Limit语句实现:
SELECT * FROM table LIMIT stratIndex,pageSize
可以看出分页主要取决于起始位置和页面数据量这两个参量,因此只需要传入这两个参量的值即可,实现方式与多参量查询类似。
UserMapper接口中定义分页查询方法:
//选择全部用户实现分页
List<User> selectUser(Map<String,Integer> map);
UserMapper配置文件中添加:
<select id="selectUser" parameterType="map" resultType="user">
select * from user limit #{startIndex},#{pageSize}
select>
测试代码:
//测试代码
//分页查询 , 两个参数startIndex , pageSize
@Test
public void testSelectUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
int currentPage = 1; //第几页
int pageSize = 2; //每页显示几个
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",(currentPage-1)*pageSize);
map.put("pageSize",pageSize);
List<User> users = mapper.selectUser(map);
for (User user: users){
System.out.println(user);
}
session.close();
}
上面这种方式是在SQL语句层面(物理层面)实现的分页操作,本质上是从数据库中拿出想要的分页数据;在java层面(逻辑层面)同样提供RowBound对象来实现分页操作,但其本质是先从数据库中取得所有数据,然后将其中用户想要的分页数据返回。
由于是在java层面实现的分页,因此在SQL层仅需查询所有数据即可
<select id="getUserByRowBounds" resultType="user">
select * from user
select>
java层面实现分页操作:
@Test
public void testUserByRowBounds() {
SqlSession session = MybatisUtils.getSession();
int currentPage = 2; //第几页
int pageSize = 2; //每页显示几个
RowBounds rowBounds = new RowBounds((currentPage- 1)*pageSize,pageSize);
//通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user: users){
System.out.println(user);
}
session.close();
}
用法比较简单,详见PageHelper文档
ResultMap元素是MyBatis中最重要最强大的元素,它可以让90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
自动映射:自动映射是不显示指定ResultMap的默认情况,例如在下面的select语句中,没有显示指定ResultMap
<select id="selectUserById" parameterType="int" resultType="map">
select * from mybatis.user where id=#{id},name=#{name}
select>
这种情况下会将数据库中所有的列映射到由ResultType属性指定的HashMap键上。
手动映射:手动映射需要用户自定义映射的类型,下面通过一个简单的示例说明
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
select>
<resultMap id="UserMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
resultMap>
上面的示例主要通过在ResultMap中自定义属性映射,来解决数据表中字段名与java Bean中定义的实体变量名不一致的问题。
除了上述操作外,结果映射可以用来解决复杂的结果映射。下面用关联查询来介绍如何进行复杂的结果映射。
假设有老师和学生两个对象,数据库设计如下
package com.lee.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String name;
private int tid;
}
package com.lee.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
假设所有配置正确完成
多对一查询
如果需要获取所有学生及对应老师的信息,有两种方式可以实现:
<select id="getStudents" resultMap="StudentTeacher">
select * from student
select>
<resultMap id="StudentTeacher" type="Student">
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
resultMap>
<select id="getTeacher" resultType="teacher">
select * from teacher where id=#{id}
select>
<select id="getStudents2" resultMap="StudentsTeacher2">
select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid=t.id
select>
<resultMap id="StudentsTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
association>
resultMap>
一对多查询
如果需要获取老师的所有学生信息,同样有两种方式可以实现:
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id=#{id}
select>
<resultMap id="TeacherStudent2" type="Teacher">
<id property="id" column="id"/>
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getTeacherById"/>
resultMap>
<select id="getTeacherById" resultType="Student">
select * from student where tid=#{id}
select>
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid=t.id and t.id=#{id}
select>
<resultMap id="TeacherStudent" type="Teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
collection>
resultMap>
注意点:
动态SQL指的是根据不同的查询条件,生成不同的SQL语句。由于在传统的JDBC框架中动态拼接SQL语句十分麻烦,MyBatis提供了动态SQL方式来实现SQL语句的动态生成,实际上可以将动态SQL语句视为是MyBatis为SQL提供的特殊编程形式,提供了以下几个标签来实现动态SQL生成。
if
choose (when, otherwise)
trim (where, set)
foreach
下面通过一个示例来说明
假设已建立博客表以及博客表相对应的实体类(Blog),mapper接口(BlogMapper),并且在MyBatis中已经进行了映射。
博客属性字段:id / title / author / createTime / views
接口类方法:List
实现要求:按title和author查询博客,其中两个都为非必选项(可为null),某项为null时只根据另一项的值进行相关查询。
BlogMapper.xml中SQL标签如下:
<sql id="if-query">
<if test="title!=null">
title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</sql>
<select id="queryBlogIf" resultType="Blog">
select * from blog
<where>
<!--在此处引用id为if-query的sql语句,等价于其替换该语句-->
<include refid="if-query"/>
</where>
</select>
上例中已经包含了where语句(
),这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
接口类方法:int updateBlog(Map map)
实现要求:类似于if,Blog的值根据不同情况进行更新。
SQL标签:
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title!=null">
title=#{title},
</if>
<if test="author!=null">
author=#{author}
</if>
</set>
where id=#{id}
</update>
接口类方法:List
实现要求:选择一个查询条件进行博客查询。
SQL标签:
<select id="queryBlogChoose" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title!=null">
title=#{title}
</when>
<when test="author!=null">
and author=#{author}
</when>
<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>
接口类方法:List
实现要求:同时查询某一字段集合的博客数据。
SQL标签:
<select id="queryBlogForEach" parameterType="map" resultType="Blog">
select * from blog
<where>
<!--用map进行传值,集合属性为ids,每次遍历的对象为id,open和close分别代表开始遍历和结束遍历时拼接的字符串,separator为遍历对象之间需要拼接的字符串-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
MyBatis使用了缓存机制来提升查询效率,用户可以非常方便的定制和配置缓存。MyBatis默认定义了两种缓存:一级缓存和二级缓存
一级缓存也称本地缓存,与数据库同一次会话查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,就会直接缓存中取出,以提升查询效率。
一级缓存是SqlSession级别的缓存,用户无法进行关闭
一级缓存失效的四种情况 (需要向数据库重新发起请求)
简单来说,一级缓存相当于只对用户本次连接的某一特定查询结果进行了缓存处理,当用户重新使用该连接执行同一操作时,就可以使用一级缓存进行结果查询。从本质上来看,一级缓存就是一个map。
由于一级缓存作用域太低,使用场景有限,因此诞生出了二级缓存,也称全局缓存。二级缓存是基于namespace级别的缓存,一个namespace对应一个二级缓存。
工作机制
如何开启?
关于缓存的配置可查看官方文档
开启二级缓存后,
第三方缓存实现
可通过导入相关jar包并在配置文件中进行相应配置,可查看不同的缓存框架文档获取相关的配置步骤,例如EhCache。