目录
1 前言
mybatis内容较简单,官方文档有中文版,学习起来较容易,本文不再过多详细介绍mybatis的使用方法,只针对一些笔者比较在意的点或者非常常用的一些方法给与示例,不再过多赘述,官网有中文详细教程:入门_MyBatis中文网
2 简介
mybatis是一个基于java的持久层框架。简而言之就是帮助程序员将数据存入数据库和从数据库中取出数据的一个东西。
传统的jdbc操作有很多重复代码,比如:数据取出时的封装。数据库的建立连接等。通过框架可以减少重复代码,提高开发效率。
mybatis是一个半自动化的ORM(Object Relationship Mapping)框架。支持普通SQL查询,存储过程和高级映射的优秀持久层框架。mybatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。
持久化:数据从瞬时状态转变为持久状态。
持久层:完成持久化工作的代码块。(dao层)
3 初识
4 基本增删改查
mybatis基本操作“增删改查”其实只有两个方法,session.seletcList(..)和session.update(..),所有的select最后都是调用的seleteList方法,增、删、改最后都是调用的update方法。不用纠结他们之间的不同,以下是部分源码,一看就懂。
package org.apache.ibatis.session.defaults;
// mybatis默认Session源码的一些常用方法简析,本类方法很多,只是列出了笔者
// 要解释的一些方法,如果笔者有兴趣,读者可以自己看完整的源码
public class DefaultSqlSession implements SqlSession {
//所有的selectOne最终都是调用的selectList方法,也就是说,其实两个方法
//是一样的,只不过暴露出来的接口好像是两个方法。
public <T> T selectOne(String statement) {
return this.selectOne(statement, (Object)null);
}
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
public <E> List<E> selectList(String statement) {
return this.selectList(statement, (Object)null);
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
public void select(String statement, Object parameter, ResultHandler handler) {
this.select(statement, parameter, RowBounds.DEFAULT, handler);
}
public void select(String statement, ResultHandler handler) {
this.select(statement, (Object)null, RowBounds.DEFAULT, handler);
}
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
this.selectList(statement, parameter, rowBounds, handler);
}
//所有的insert方法调用的都是update方法,也就是说在使用插入和修改时,调
//用的是一样的方法,无论是插入操作还是更新操作,在调用insert和update没
//有本质区别,在调用这两个方法时,只要数据库中的条件满足where都是更新操
//作,只要没有满足的数据,都是插入操作,不用纠结这俩方法有什么不同,只不
//过封装后暴露出来的接口看着一个insert,一个update,看起来更人性化。
public int insert(String statement) {
return this.insert(statement, (Object)null);
}
public int insert(String statement, Object parameter) {
return this.update(statement, parameter);
}
public int update(String statement) {
return this.update(statement, (Object)null);
}
public int update(String statement, Object parameter) {
int var4;
try {
this.dirty = true;
MappedStatement ms = this.configuration.getMappedStatement(statement);
var4 = this.executor.update(ms, this.wrapCollection(parameter));
} catch (Exception var8) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8);
} finally {
ErrorContext.instance().reset();
}
return var4;
}
//没有想到吧,就连delete方法最终调用的也是update,所以说mybatis基本操
//作增删改其实都是使用的update操作,本质没有什么区别,当操作增删改时调用
//(insert/update/delete)都一样,只不过习惯上增加调用insert,修改调用update,
//删除调用delete,都是错觉。
public int delete(String statement) {
return this.update(statement, (Object)null);
}
public int delete(String statement, Object parameter) {
return this.update(statement, parameter);
}
}
当插入一条数据时,普通配置如下:插入的所有值都需要设置,不设置的为空。
<insert id="insert" parameterType="com.evenif.bean.Index">
insert into tbl_index(id,`name`)values(#{id},#{name})
</insert>
如果使用自增长ID则需要配置useGeneratedKeys="true",一般与keyProperty同时使用,这样数据插入之后,bean会自动获得ID,如果没有keyProperty,则不会获取。
<!-- userGeneratedKeys :自动插入ID keyProperty:如果不使用改属性,插入数据之后,bean实例不会自动填充自动增长的ID,使用改属性,插入后bean实例会自动获得插入后的id值-->
<insert id="insert" parameterType="com.evenif.bean.Index" useGeneratedKeys="true" keyProperty="id">
insert into tbl_index(id,`name`)values(#{id},#{name})
</insert>
例如使用以上配置之后index = new Index(id=0,name=index01),插入事务执行后index={id=1,name=index01},如果没有配置keyProperty属性,则插入事务执行后index={id=0,name=index01}
实例代码: Mybatis实例参考02-增删改查
5 配置文件解析
5.1 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指mybatis可以配置多个环境 default指向默认的环境
每个SqlSessionFactory对应一个环境environment -->
<environments default="development">
<environment id="development">
<!-- JDBC - 这个配置直接使用JDBC的提交和回滚功能。它依赖于从
数据源获得连接来管理事务的生命周期。
MANAGER - 这个配置基本上什么都不做。它从不提交或者回滚一个连接
的事务。而是让容器(例如:Spring或者J2EE应用服务器)来管理事务
的生命周期-->
<transactionManager type="JDBC"/>
<!-- 数据源类型:
UNPOOLED - 这个类型的数据源实现只是在每次需要的时候简单地打
开和关闭连接。
POOLED - 这个数据源的实现缓存了JDBC连接对象,用于避免每次创
建新的数据库连接时都初始化和连接认证,加快程序响应。并发WEB应
用通常通过这种做法来获得快速响应。
JNDI - 这个数据源的配置是为了准备与像Spring或应用服务器能够在
外部或者内部配置数据-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 配置javaBean对应的mapper文件的位置-->
<mapper resource="IndexMapper.xml"/>
</mappers>
</configuration>
5.2 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">
<!-- namespace命名空间 防止sql语句的id重名
namespace命名:包名+类名+mapper文件名
parameterType : sql语句参数类型
resultType: 返回结果类型
userGeneratedKeys="true" 使用自增主键 -->
<mapper namespace="IndexMapper">
<select id="getById" resultType="com.evenif.bean.Index">
select * from tbl_index where id=#{id}
</select>
<select id="loadAll" resultType="com.evenif.bean.Index">
select * from tbl_index
</select>
<!-- userGeneratedKeys :自动插入ID keyProperty:如果不使用改属性,插入数据之后,bean实例不会自动填充自动增长的ID,使用改属性,插入后bean实例会自动获得插入后的id值-->
<insert id="insert" parameterType="com.evenif.bean.Index" useGeneratedKeys="true" keyProperty="id">
insert into tbl_index(id,`name`)values(#{id},#{name})
</insert>
<update id="update" parameterType="com.evenif.bean.Index">
update tbl_index set `name`=#{name} where id=#{id}
</update>
<delete id="delete">
delete from tbl_index where id=#{id}
</delete>
</mapper>
执行流程:
读取核心配置文件mybatis-config.xml→sqlSessionFactory→sqlSession→执行相关操作
5.3 优化DB配置
新加一个db.properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useSSL=false
username=root
password=root
修改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>
<!-- 新加配置,指向db配置路径 -->
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 使用${属性名}来获取db.properties中的值-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="IndexMapper.xml"/>
</mappers>
</configuration>
优化之后执行效果和5.1一样,只是吧数据库连接配置分离出来,方便管理。
5.4 给javaBean起别名
可以给javaBean起别名,在mapper.xml中简化引用,笔者个人不太喜欢,因为其实长不了多少,全类名看的时候比较清晰直观知道哪个类。
5.4.1 方式一
<?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>
<!-- 其他配置
...
-->
<typeAliases>
<typeAlias type="com.evenif.bean.Index" alias="Index"/>
</typeAliases>
<!-- 其他配置
...
-->
</configuration>
5.4.2 方式二
<?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>
<!-- 其他配置
...
-->
<!-- 别名方式二:给包下所有类名指定别名,默认别名是对应的类名-->
<package name="com.evenif.bean"/>
<!-- 其他配置
...
-->
</configuration>
6 类名与数据库字段名不一致的解决方法
因为mybatis会自动映射数据库的字段名与java类中的同名属性名,java类中设置的属性名称和数据库中的列名称不一致时则无法映射。解决方法比较简单,一种就是编写sql语句时给列名起别名,一种是在mapper配置中设置结果的映射集,推荐第二种方式,不用在每个sql语句中都要起别名。
小提示:
笔者在实际的开发环境中一般列名都不会和类中字段名称一致,例如name字段,在数据库中为关键字,可能会引起不必要的麻烦,所以笔者在数据库中列名通常都是使用格式c_columnName。表名称一般会是t_tableName。虽然可能有点麻烦,但会避免字段名一旦是数据库关键字,有问题时不一定能发现。
6.1 为列名指定别名
完整示例代码:Mybatis实例参考04-列名与属性名不一致的解决方法
<!-- #################### 给列名起别名解决列名和属性名不一致 #################### -->
<select id="getById0" resultType="com.evenif.bean.Person">
select c_id id,c_name name,c_age age from t_person where c_id=#{id}
</select>
6.2 设置结果映射集
完整示例代码:Mybatis实例参考04-列名与属性名不一致的解决方法
<resultMap id="PersonMap" type="com.evenif.bean.Person">
<!-- id为主键 -->
<id column="c_id" property="id"/>
<!-- column是数据库表的列名,property是对应实体类的属性名 -->
<result column="c_name" property="name"/>
<result column="c_age" property="age"/>
</resultMap>
<!-- #################### 结果集映射(推荐)#################### -->
<select id="getById" resultMap="PersonMap">
select c_id ,c_name ,c_age from t_person where c_id=#{id}
</select>
mybatis列名与java类属性自动映射规则:
1.会将数据库列名自动转换为全小写来映射java类中的同名属性(java类中的属性不会转小写,定义的是什么就是什么,大小写不通用)
2.如果是自动映射,java类中必须有标准格式的setter方法。mybatis是通过setter方法进行的自动映射。
7 分页查询
示例代码:Mybatis实例参考05-分页查询
7.1 使用limit语句
推荐使用limit语句,是物理分页,也就是设置读取几条就读取几条数据到内存。
<!-- 第一种方式: start和size参数是放入到Map集合中传递过来的,
parameterType="map" 设置可以省略-->
<select id="loadAll" parameterType="map" resultMap="PersonMap">
select c_id ,c_name ,c_age from t_person limit #{start},#{size}
</select>
<!-- 第二种方式:也可以定义类,例如 PageQuery{}( int start;int size) -->
<select id="loadAll" parameterType="PageQuery" resultMap="PersonMap">
select c_id ,c_name ,c_age from t_person limit #{start},#{size}
</select>
7.2 使用RowBounds
RowBounds类是mybatis自带的分页查询。在sql语句上面不用做任何设置,因此,很显然,是把数据全部读取到内存之后,做的逻辑分页。笔者不推荐使用。但是不绝对,对于总数据量小,对查询速度要求高的场景适用。
<select id="loadWithRowBounds" resultMap="PersonMap">
select c_id ,c_name ,c_age from t_person
</select>
RowBounds rowBounds = new RowBounds(0,2);
List<Person> list = session.selectList("PersonMapper.loadWithRowBounds",null,rowBounds);
8 使用注解实现mybatis
mybatis的一个重要特征就是dao层都是接口,根据配置(xml或是注解的sql语句),调用时自动通过反射获得实现方法。
面向接口编程的好处:拓展性好,分层开发中,上层不用管具体的实现,大家都遵循共同的标准,使得开发变得容易,规范性更好。
完整示例代码:Mybatis实例参考06-使用注解实现mybatis
使用注解时没有xml配置文件,所以mybatis-config.xml需要配置类名如下:
<mappers>
<!-- 使用注解时配置javaBean的位置-->
<mapper class="com.evenif.dao.IndexDao"/>
</mappers>
8.1 多个参数
当注解中的sql语句使用多个参数时,需要使用到“org.apache.ibatis.annotations.Param”注解来标识java方法中的参数名称,sql语句中才能使用同名获取,否则只能按顺序使用#{0},#{1}。mybatis可以自动识别映射基本类型。如果参数既有复杂类又有基本类型,建议直接封装成一个类。否则当然也可以通过注解实现,但是不建议。笔者建议要么全是基本类型,当参数过多时,将参数封装为一个类(如8.3)。
@Select("select * from tbl_index limit #{start},#{size}")
List<Index> loadAll(@Param("start") int start, @Param("size") int size);
8.2 单个参数
使用注解时比较像mybatis-spring的形式,如果是一个参数,自动识别,写#{id}或#{0}都能取得参数,其实和参数名称无关,例如以下定义int iddd也是可以的,当然一般都是一致的定义为int id 和#{id}
@Delete("delete from tbl_index where id=#{id}")
void delete(int iddd);
8.3 参数为类
参数为类,则#取值时自动映射同名属性。推荐使用。
@Select("insert into tbl_index(id,`name`)values(#{id},#{name})")
void insert(Index index);
以下待整理,临时想到的
稍显复杂的条件判定,遇到时经常查文档,不能直接写出来,关于集合的in条件操作
<if test="mySet != null">
<foreach collection="mySet" item="aaa" open=" and c_xxx in (" close=")" separator=",">
#{aaa}
</foreach>
</if>
“>”和“<”是关键字,在mapper文件中的sql语句部分不能使用,需要使用转义字符
> 需要写成 > 例如c_aaa > 1 需要写成 c_aaa > 1
< 需要写成 < 例如c_aaa < 1 需要写成 c_aaa < 1
>=需要写成>=. 例如c_aaa >= 1 需要写成 c_aaa >= 1
<=需要写成<=. 例如c_aaa <= 1 需要写成 c_aaa <= 1
where关键字和_paramerter关键字解释
<where>
<if test="_parameter.param1 > 0">
c_id = #{0}
</if>
<if test="_parameter.param2 != null">
c_name = #{1}
</if>
<if test="_parameter.param3 != null">
<foreach collection="_parameter.param3" item="i" open=" and c_phones in (" close=")" separator=",">
#{i}
</foreach>
</if>
</where>
如果参数只有一个Integer类型写_parameter.param1 > 0这种格式会报错
不存在:Java.lang.Integer.param1 这种格式
直接写成:
_parameter > 0是正确的
COALESCE(sum(column),0)
当sql语句是count或sum,返回类型为Integer/Long等类型时,为空不能自动拆箱,异常,使用coalesce来处理