MyBatis知识点
框架
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。具有约束性的去支持我们完成各种功能的半自动的ORM框架(框架 + 业务逻辑 = 完整项目)。
- 具有约束性定义标准
- 支持自定义 SQL、存储过程以及高级映射
- 持久层框架
- 整合型框架/设计型框架
简介
1、支持定制化SQL、存储过程以及高级映射的优秀的持久层框架
2、优点
- 避免了几乎有所的JDBC代码(对JDBC进行封装)
- 避免了手动设置参数:不需要设置通配符,设参方式
${}
、#{}
3、使用简单的XML或注解用于配置和映射
- XML:自定义SQL(推荐使用)
- 注解:SQL语句需放在Mapper接口的方法中,不易更改(不推荐使用)
4、半自动的ORM(Object Relation Mapping)框架
- Object:对象
- Relation:关系型数据库中的一条记录
- 两者创建映射关系:接口和POJO(Java对象)映射成数据库中的记录
5、与现有持久层框架的对比
- JDBC:
-
- 开发效率低:
加载驱动 > 创建连接 > 创建预编译对象 > 通配符赋值 > 执行SQL语句 > 处理结果
- 开发效率低:
-
- 执行效率低:使用过程需要解析
- Hibernate:
-
- 开发难度大
-
- 自动生成的SQL语句不易优化
-
- 开发效率低,执行效率低
- MyBatis:
-
- 开发容易
-
- SQL语句易优化
-
- 解耦
安装
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
1、依赖
<!-- spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
<!-- springboot -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>x.x.x</version>
</dependency>
2、XML核心配置
从 XML 中构建SqlSessionFactory
,配置数据源 DataSource
、事务管理器 TransactionManager
和 映射器 mapper
<?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="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 驼峰命名配置 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
也可以通过Java代码实现构建 ```SqlSessionFactory``, MyBatis也提供了完整的配置类。不推荐使用,因为这样使MyBatis的映射增强了复杂性。
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
3、映射SQL语句
映射方式有两种:一种是通过XML定义,另一种是通过注解的方式。
3.1、通过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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
namespace: 在命名空间 org.mybatis.example.BlogMapper
中定义了一个名为 selectBlog
的映射查询语句。命名空间必须用全限定名,并且必须指定命名空间。
最终是通过 org.mybatis.example.BlogMapper.selectBlog
来调用映射语句的,等同于
SqlSession session = sqlSessionFactory.openSession()
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
// 或者
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
命名空间的作用:
- 利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定
- 命名空间使代码变得更加整洁,也有利于更方便地使用MyBatis
3.2、通过注解定义
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句就比较空难了。
推荐使用XML的方式来映射语句,如果项目没有要求的话可以组合使用(简单可以使用注解方式,复杂的可以使用XML方式)。
4、作用域和生命周期
因为错误的使用作用域和生命周期会导致非常严重的并发问题。
例如我们使用spring框架整合时,可以创建线程安全的、基于事务的SqlSession和映射器,并将它们直接注入到你的bean中,这时我们可以直接忽略它们的生命周期。
4.1、SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 只是一个辅助性的类,我们创建了SqlSessionFactory,就可以不再需要了。
因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
可以使用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,最好不要一直保留着它,保证所有的XML解析资源可以被释放。
4.2、SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
4.3、SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中。
关闭是非常重要的,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
配置
1、properties(属性)
属性可以在外部进行配置,并可以进行动态替换。
<properties resource="com/mybatis/example/config.properties">
<property name="username" value="admin"/>
<property name="password" value="123456"/>
</properties>
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 如果属性 'password' 没有被配置,'password' 属性的值将为 '123456' -->
<property name="password" value="${password:123456}"/>
<!-- 如果在属性名中使用了 ":" 字符(如:db:password),或者在 SQL 映射中使用了 OGNL 表达式的三元运算符(如: ${tableName != null ? tableName : 'global_constants'}),就需要设置特定的属性来修改分隔属性名和默认值的字符 -->
<property name="password" value="${db:password?:123456}"/>
</dataSource>
一个属性在多个地方配置了,它的加载顺序是:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
2、settings(设置)
此属性是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为。
设置名 | 描述 | 默认值 |
---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存 | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载 | false |
multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持) | true |
useColumnLabel | 使用列标签代替列名,实际表现依赖于数据库驱动 | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。 | false |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套) |
PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE 不做任何反应;WARNING 输出警告日志;FAILING 映射失败 |
NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句; BATCH 执行器不仅重用语句还会执行批量更新。 |
SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 未设置 (null) |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 未设置 (null) |
defaultResultSetType | 指定语句默认的滚动策略: FORWARD_ONLY 、 SCROLL_SENSITIVE 、 SCROLL_INSENSITIVE 、DEFAULT |
未设置 (null) |
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false | true |
safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false | true |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | false |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION ,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT ,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 |
SESSION |
jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL 、VARCHAR 或 OTHER 。 |
OTHER |
lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成使用的默认脚本语言 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler |
org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联) | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找 | 未设置 |
proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具 | JAVASSIST |
vfsImpl | 指定 VFS 的实现 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项 |
true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法 |
未设置 |
shrinkWhitespacesInSql | 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串 | false |
defaultSqlProviderType | 指定保存提供程序方法的sql提供程序类(自3.5.6起)。此类应用于sql提供程序批注(例如@SelectProvider)上的类型(或值)属性,但这些属性被忽略 | 未设置 |
例如:
<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>
3、typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写
<typeAliases>
<typeAlias alias="Author" type="com.entity.Author"/>
<typeAlias alias="Comment" type="com.entity.Comment"/>
</typeAliases>
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="com.entity.Author"/>
</typeAliases>
每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值
@Alias("author")
public class Author {
}
4、typeHandlers(类型处理器)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。
具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。
ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
mybatis-config.xml
<typeHandlers>
<typeHandler handler="com.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:
- 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String");
- 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。
可以通过两种方式来指定关联的 JDBC 类型:
- 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");
- 在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。
5、处理枚举类型
若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选择一个来使用。
注意 EnumTypeHandler 在某种意义上来说是比较特别的,其它的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。
在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形数值。
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>
自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler 来处理枚举类型, 所以如果我们想用普通的 EnumTypeHandler,就必须要显式地为那些 SQL 语句设置要使用的类型处理器。
<mapper namespace="com.mybatis.example.UserMapper">
<resultMap type="com.mybatis.example.User" id="usermap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="funkyNumber" property="funkyNumber"/>
<result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
</resultMap>
<select id="getUser" resultMap="usermap">
select * from users
</select>
<insert id="insert">
insert into users2 (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
)
</insert>
</mapper>
注意,这里的 select 语句必须指定 resultMap 而不是 resultType。
6、objectFactory(对象工厂)
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。
默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。
如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}
}
mybatis-config.xml
<objectFactory type="com.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
7、plugins(插件)
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class)})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// 预处理前
Object returnObject = invocation.proceed();
// 预处理后
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
8、environments(环境配置)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。
如开发、测试和生产环境需要有不同的配置,或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
8.1、每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
// 如果忽略了环境参数,那么将会加载默认环境
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
environments 元素定义了如何配置环境
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
关键点:
- 默认使用的环境 ID(比如:default="development")
- 每个 environment 元素定义的环境 ID(比如:id="development")
- 事务管理器的配置(比如:type="JDBC")
- 数据源的配置(比如:type="POOLED")
8.2、事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器 type="[JDBC|MANAGED]"
:
- DBC: 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED: 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
8.3、数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
有三种内建的数据源类型 type="[UNPOOLED|POOLED|JNDI]"
:
UNPOOLED 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
- driver: 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
- url: 这是数据库的 JDBC URL 地址。
- username: 登录数据库的用户名。
- password: 登录数据库的密码。
- defaultTransactionIsolationLevel: 默认的连接事务隔离级别。
- defaultNetworkTimeout: 等待数据库操作完成的默认网络超时时间(单位:毫秒)。
作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可。例如:driver.encoding=UTF8
POOLED 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
配置 POOLED 的数据源:
- poolMaximumActiveConnections: 在任意时间可存在的活动(正在使用)连接数量,默认值:10
- poolMaximumIdleConnections: 任意时间可能存在的空闲连接数。
- poolMaximumCheckoutTime: 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
- poolTimeToWait: 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
- poolMaximumLocalBadConnectionTolerance: 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过
poolMaximumIdleConnections
与poolMaximumLocalBadConnectionTolerance
之和。 默认值:3(新增于 3.4.5) - poolPingQuery: 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
- poolPingEnabled: 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
- poolPingConnectionsNotUsedFor: 配置
poolPingQuery
的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当poolPingEnabled
为 true 时适用)。
JNDI 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
这种数据源配置只需要两个属性:
- initial_context – 这个属性用来在
InitialContext
中寻找上下文(即,initialContext.lookup(initial_context)
)。这是个可选属性,如果忽略,那么将会直接从InitialContext
中寻找data_source
属性。 - data_source – 这是引用数据源实例位置的上下文路径。提供了
initial_context
配置时会在其返回的上下文中进行查找,没有提供时则直接在InitialContext
中查找。
和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给InitialContext
。比如:env.encoding=UTF8
9、databaseIdProvider(数据库厂商标识)
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。
为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
<databaseIdProvider type="DB_VENDOR" />
databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName()
返回的字符串
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
10、mappers(映射器)
定义 SQL 映射语句的方式:
使用相对于类路径的资源引用
<mappers>
<mapper resource="com/mybatis/example/AuthorMapper.xml"/>
<mapper resource="com/mybatis/example/BlogMapper.xml"/>
<mapper resource="com/mybatis/example/PostMapper.xml"/>
</mappers>
使用完全限定资源定位符(URL)
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="com.mybatis.example.AuthorMapper"/>
<mapper class="com.mybatis.example.BlogMapper"/>
<mapper class="com.mybatis.example.PostMapper"/>
</mappers>
将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="com.mybatis.example"/>
</mappers>
XML 映射器
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。
如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素:
- ache: 该命名空间的缓存配置。
- cache-ref: 引用其它命名空间的缓存配置。
- resultMap: 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
- parameterMap: 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
- sql: 可被其它语句引用的可重用语句块。
- insert: 映射插入语句。
- update: 映射更新语句。
- delete: 映射删除语句。
- select: 映射查询语句。
1、select
查询语句是 MyBatis 中最常用的元素之一,个简单查询的 select 元素是非常简单的。比如:
<select id="selectPerson" parameterType="int" resultType="map">
SELECT * FROM USER WHERE ID = #{id}
</select>
这个语句名为 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
元素配置属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset) |
resultType | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false |
useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动) |
fetchSize | 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。 |
resultSets | 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
2、insert、update和delete
数据变更语句 insert,update 和 delete 的实现非常接近
<insert id="insertAuthor" parameterType="com.mybatis.example.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor" parameterType="com.mybatis.example.Author" flushCache="true" statementType="PREPARED" timeout="20">
update Author set username = #{username}, password = #{password}, email = #{email}
where id = #{id}
</update>
<delete id="deleteAuthor" parameterType="com.mybatis.example.Author" flushCache="true" statementType="PREPARED" timeout="20">
delete from Author where id = #{id}
</delete>
select元素配置属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | 这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | 指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | 设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
3、sql
这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
这个 SQL 片段可以在其它语句中使用
select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
4、字符串替换
默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ?
一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。
比如 ORDER BY
子句,这时候可以使用 ${}
:
ORDER BY ${columnName}
#与 $区别:
- 使用#{}意味着使用的预编译的语句,即在使用jdbc时的preparedStatement,sql语句中如果存在参数则会使用?作占位符,这种方式可以防止sql注入,并且在使用#{}时形成的sql语句,已经带有引号。
- 使用${}时的sql不会当做字符串处理,会带来sql注入。
5、缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
以通过 cache 元素的属性来修改
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
此配置为创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
eviction 可用的清除策略有:
-
LRU(最近最少使用):移除最长时间不被使用的对象,默认。
-
FIFO(先进先出):按对象进入缓存的顺序来移除它们。
-
SOFT(软引用):基于垃圾回收器状态和软引用规则移除对象。
-
WEAK(弱引用):更积极地基于垃圾收集器状态和弱引用规则移除对象。
-
flushInterval(刷新间隔):正整数,以毫秒为单位
-
size(引用数目):任意正整数,默认值是 1024
-
readOnly(只读):默认值false
使用自定义缓存
<cache type="com.mybatis.example.MyCustomCache"/>
type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器
动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
1、IF
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分
<select id="getUser" resultType="User">
SELECT * FROM USER
WHERE state = '1'
<if test="userName != null and userName != ''">
AND user_name like #{userName}
</if>
</select>
如果不传入userName
,那么所有处于 1
状态的 User 都会返回;如果传入了userName
参数,那么就会对userName
一列进行模糊查找并返回对应的 User 结果。
2、choose、when、otherwise
MyBatis提供的choose元素,有点像Java中的switch语句
<select id="getUser" resultType="User">
SELECT * FROM USER WHERE 1 = 1
<choose>
<when test="userName != null and userName != ''">
AND user_name like #{userName}
</when>
<when test="state != null">
AND state = #{age}
</when>
<otherwise>
AND state = '1'
</otherwise>
</choose>
</select>
如果不传 state
就按照为1
的返回结果, 如果传了,已传的具体值为条件返回结果。
3、trim、where、set
SELECT * FROM USER WHERE
<if test="state != null">
AND state = #{state}
</if>
如果IF中的state
都不传,最终的SQL语句将是这样的:
SELECT * FROM USER WHERE
这个查询会失败,这时我们可以这样解决:
SELECT * FROM USER
<where>
<if test="state != null">
AND state = #{state}
</if>
</where>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
<update id="updateAuthor">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password}</if>
</set>
where id=#{id}
</update>
4、foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
<select id="selectPostIn" resultType="com.mybatis.example.User">
SELECT * FROM USER
WHERE ID in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符,这个元素也不会错误地添加多余的分隔符。
5、script
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password}</if>"
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
6、bind
bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文
<select id="selectUserLike" resultType="User">
<bind name="userName" value="'%' + _parameter.getUserName() + '%'" />
SELECT * FROM USER
WHERE user_name LIKE #{userName}
</select>
7、 多数据库支持
如果配置了 databaseIdProvider,可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句
<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'db2'">
select nextval for seq_users from sysibm.sysdummy1"
</if>
</selectKey>
insert into users values (#{id}, #{name})
</insert>
日志
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>
可选的值有:SLF4J
、LOG4J
、LOG4J2
、JDK_LOGGING
、COMMONS_LOGGING
、STDOUT_LOGGING
、NO_LOGGING
,或者是实现了 org.apache.ibatis.logging.Log
接口,且构造方法以字符串为参数的类完全限定名。
配置 Log4J 实例:
在应用的类路径中创建一个名为 log4j.properties 的文件,文件的具体内容如下:
# 全局日志配置
log4j.rootLogger=ERROR, stdout
# MyBatis 日志配置
com.mybatis.example=DEBUG
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n