MyBatis 是一款优秀的持久层框架,是Apache的一个Java开源项目 ,它支持自定义 SQL、存储过程以及高级映射, 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。 MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。
目前Mybatis能够在githup上直接下载
要使用Mybatis只需要将 mybatis-xxx.jar 文件置于类路径(classpath)中即可。
maven依赖如下(除了mybatis外,还需要导入和数据库版本适配的驱动包)
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- database connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
创建 mybatis-config.xml 配置文件,一般直接置于resource目录下。
该文件包含了对Mybatis系统的核心设置,包括获取数据库连接实例的数据源(DataSource)、决定事务作用域和控制方式的事务管理器(TransactionManager)。
首先简单配置如下:
<?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>
<!-- 和spring整合后 environments配置将废除 -->
<!--环境配置,可以配置多个,根据id选择默认环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理 -->
<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>
<!-- 配置文件映射,指定配置文件的位置,每个mapper都需要在核心配置文件中注册后才可使用 -->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
每个基于Mybatis的应用都是以一个SqlSessionFactory的实例为核心的。
而SqlSessionFactory的实例则可以通过SqlSessionFactoryBuilder获得。
而SqlSessionFactoryBuilder则可以从XML配置文件中构建
示例代码如下:
public class MybatisUtils {
// 声明静态属性
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();
}
}
/**
* 获取SqlSession对象
*/
public static SqlSession getSqlSession() {
// 根据工厂类创建SqlSession对象并返回
return sqlSessionFactory.openSession();
}
创建POJO对象
public class User {
private int id;
private String name;
private String pwd;
/**
* 加上对应的构造方法、getters&setters、toString()
*/
}
创建持久层接口
public interface UserMapper {
User getUserById(int id);
}
创建对应的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 命名空间,绑定一个对应的 Mapper 接口,且为全限定类名 -->
<mapper namespace="com.kuang.dao.UserMapper">
<!-- id 为指定接口的方法,parameterType为传入参数类型,resultType为返回值类型 -->
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from user user where id = #{id}
</select>
</mapper>
在核心配置文件中注册
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>
测试
使用Junit进行测试
@Test
public void test3() {
// 调用自己编写的工具类获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 传入接口的字节码文件,创建代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行方法,并传入参数
User u = userMapper.getUserById(1);
// 打印
System.out.println(u);
// 释放资源
sqlSession.close();
}
详解
具体步骤如下:
MyBatis
JDBC
连接池技术管理
手动地、频繁地创建和释放连接,浪费系统资源
SQL和代码分离,易于维护
SQL经常变动,字符串拼接繁琐且容易出错
自动将java对象映射到sql语句,自动实现结果封装
SQL语句的set内容、where条件都不一定,参数个数不明确,十分麻烦,且需要手动遍历封装
SqlSessionFactoryBuilder
SqlSessionFactory
SqlSession
映射器实例
映射器是一些绑定映射语句的接口,接口实例从SqlSession中获得
合适的作用域是方法作用域,即映射器应该在调用它们的方法中被获取,使用完毕后即丢弃
MyBatis的配置文件十分重要!
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
外部文件
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="zhao"/>
</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>
其中,username和password都会被替换
属性加载顺序
综上,优先级排名:方法参数 > 外部文件 > 大于核心配置文件
设置默认值
从MyBatis3.4.2开始,可以为占位符指定一个默认的值,如:
<dataSource type="POOLED">
<property name="username" value="${username:ut_user}"/> <!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
</dataSource>
该特性默认关闭,若要使用,需先配置,如:
<properties resource="org/mybatis/example/config.properties">
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- 启用默认值特性 -->
</properties>
但开启后,如果在属性命名中用到了 “:”,它就会被识别为默认值分隔符,鉴于 “:”,在三元运算等诸多场合都会被用到,所以需要修改默认值分隔符,也就是说,令找一种表示方式来代替 “:”,如
<properties resource="org/mybatis/example/config.properties">
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- 修改默认值的分隔符 -->
</properties>
<!-- 此时在属性获取内容处,应表达如下-->
<dataSource type="POOLED">
<property name="username" value="${db:username?:ut_user}"/>
</dataSource>
这是MyBatis中极为重要的调整,会改变MyBatis的运行时行为。
设置名
描述
有效值
默认值
cacheEnabled
全局性地开启或关闭缓存
true | false
true
lazyLoadingEnabled
设置延迟加载,关联关系可通过设置 fetchType 属性来覆盖
true | false
false
aggressiveLazyLoading
任何方法调用都会加载该对象的所有延迟加载属性,否则延迟加载属性按需加载
true | false
false
multipleResultSetsEnabled
单个语句返回多个结果集
true | false
true
useColumnLabel
使用列标签代替列名
true | false
true
useGeneratedKeys
允许JDBC支持自动生成主键
true | false
false
autoMappingBehavior
自动将列映射到字段或属性
NONE:关闭自动映射
PARTIAL:自动映射没有定义嵌套结果映射的字段
FULL:自动映射任何复杂的结果集
NONE, PARTIAL, FULL
PARTIAL
autoMappingUnknownColumnBehavior
指定发现自动映射目标未知列的行为
NONE:不做任何反应
WARNING:输出警告日志
FAILING:映射失败
NONE, WARNING, FAILING
NONE
defaultExecutorType
配置默认执行器
SIMPLE:普通执行器
REUSE:重用预处理语句(PreparedStatement)
BATCH:既重用语句又批量更新
SIMPLE, REUSE, BATCH
SIMPLE
defaultStatementTimeout
超时时间
任意正整数
null
defaultFetchSize
为驱动的结果集获取数量(fetchSize)设定一个建议值
任意正整数
null
defaultResultSetType
指定语句默认的滚动策略
FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(即未设置)
null
safeRowBoundsEnabled
是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false
true | false
false
sageResultHandlerEnabled
是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false
true | false
false
mapUnderscoreToCamelCase
是否开启驼峰命名自动映射
true | false
false
localCacheScope
本地缓存机制的类型
SESSION:缓存一个会话中执行的所有查询
STATEMENT:仅对单个语句执行进行缓存,但对相同SqlSession的不同查询不会缓存
SESSION | STATEMENT
SESSION
jdbcTypeForNull
指定列的JDBC类型
JDBC常量,如:NULL, VARCHAR, OTHER
OTHER
lazyloadTriggerMethods
指定对象触发延迟加载的条件
用逗号分隔的方法列表
equals, clone, hashCode, toString
defaultScriptingLanguage
指定动态SQL生成使用的默认脚步语言
类型别名或全类名
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler
指定 Enum使用的默认 TyperHandlder
类型别名或全类名
org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls
指定当结果集中值为null时是否调用映射对象的 setter 方法(基本类型不能设置为null)
true | false
false
returnInstanceForEmptyRow
当返回行的所有列都是空时,MyBatis默认返回空实例对象
true | false
false
logPrefix
指定MyBatis增加到日志名称的前缀
任何字符串
null
logImpl
指定MyBatis所用日志的具体实现,未指定时自动查找
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
null
proxyFactory
指定MyBatis创建可延迟加载对象多用到的代理工具
CGLIB | JAVASSIST
JAVASSIST
vfsImpl
指定 VFS 的实现
自定义 VFS 的实现类全类名,以逗号分隔
null
useActualParamName
允许使用方法签名中的名称作为语句参数名称
true | false
true
configurationFactory
指定一个提供 Configuration 实例的类,用来加载被反序列化对象的延迟加载属性值(该类必须包含一个 static Configuration getConfiguration()方法)
类的别名或全类名
null
shrinkWhitespacesInSql
从SQL中删除多余的空格(会影响SQL中的文字字符串)
true | false
false
defaultSqlProviderType
指定一个类作为SQL语句提供类,需使用 @SelectProvider注解
类的别名或全类名
null
<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>
给Java类型设置一个缩写的名字,仅用于XML配置,可减少重复,例:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
也可以指定一个包名,MyBatis会在包名下面自动搜索需要的Java类型,此时会使用类的首字母小写的类名作为别名。例:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
此时也可以单独给类设置别名,使用 @Alias注解
@Alias("author")
public class Author {
...
}
常见的Java类型别名(不区分大小写):
别名
映射类型
_byte
byte
_long
long
_short
short
_int | _integer
int
_double
double
_float
float
_boolean
boolean
string
String
byte
Byte
long
Long
short
Short
int | integer
Integer
double
Double
float
Float
boolean
Boolean
date
Date
decimal | bigdecimal
BigDecimal
object
Object
map
Map
hashmap
HashMap
list
List
arraylist
ArrayList
collection
Collection
iterator
Iterator
类型处理器用于将获取到的值转换为Java类型
类型处理器
Java类型
JDBC类型
BooleanTypeHandler
Boolean | boolean
BOOLEAN
ByteTypeHandler
Byte | byte
BYTE | NUMERIC
ShortTypeHandler
Short | short
SMALLINT | NUMERIC
IntegerTypeHandler
Integer | int
INTEGER | NUMERIC
LongTypeHandler
Long | long
BIGINT | NUMERIC
FloatTypeHandler
Float | float
FLOAT | NUMERIC
DoubleTypeHandler
Double | double
DOUBLE | NUMERIC
BigDecimalTypeHandler
BigDecimal
DECIMAL | NUMERIC
StringTypeHandler
String
CHAR | VARCHAR
ClobReaderTypeHandler
Reader
-
ClobTypeHandler
String
CLOB | LONGVARCHAR
NStringTypeHandler
String
NVARCHAR | NCHAR
NClobTypeHandler
String
NCLOB
BlobInputStreamTypeHandler
InputStream
-
ByteArrayTypeHandler
byte[ ]
数据库兼容的字节流类型
BlobTypeHandler
byte[ ]
BLOB | LONGVARBINARY
DateTypeHandler
Date
TIMESTAMP
DateOnlyTypeHandler
Date
DATE
TimeOnlyTypeHandler
Date
TIME
SqlTimestampTypeHandler
Timestamp
TIMESTAMP
SqlDateTypeHandler
Date
DATE
SqlTimeTypeHandler
Time
TIME
ObjectTypeHandler
Any
-
EnumTypeHandler
Enumeration Type
VARCHAR或任何能兼容的字符串类型,用来存储枚举的名称
EnumOrdinalTypeHandler
Enumeration Type
兼容 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值
SqlxmlTypeHandler
String
SQLXML
InstantTypeHandler
Instant
TIMESTAMP
LocalDateTimeTypeHandler
LocalDateTime
TIMESTAMP
LoalDateTypeHandler
LocalDate
DATE
LocalTimeTypeHandler
LocalTime
TIME
OffsetDateTimeTypeHandler
OffsetDateTime
TIMESTAMP
OffsetTimeTypeHandler
OffsetTime
TIME
ZonedDateTimeTypeHandler
ZonedDateTime
TIMESTAMP
YearTypeHandler
Year
INTEGER
MonthTypeHandler
Month
INTEGER
YearMonthTypeHandler
YearMonth
VARCHAR | LONGVARCHAR
JapaneseDateTypeHandler
JapaneseDate
DATE
每次MyBatis创建结果对象的新实例时,都会使用一个对象工厂实例来完成实例化工作。
如果想要覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现,
创建实例的方法 create(),包括无参构造和有参构造
配置工厂的方法 setProperties(),在初始化ObjectFactory后,元素体中定义的属性会作为参数传递过来
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List
MyBatis可使用插件在映射语句执行的过程中的某一点处进行拦截调用,方法包括:
自定义插件时,要特别小心,有可能会破坏MyBatis的核心模块
// ExamplePlugin.java
@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 {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
MyBatis内部可配置多种环境,便于SQL映射应用于多个数据库中。但是,对于每个SqlSessionFactory实例,只能选择一种环境。
因此,如果想要连接多个数据库进行操作,就要创建多个SqlSessionFactory实例,每个数据库对应一个。
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, 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>
其中
在Mybatis中有两种类型的事务管理器:
JDBC:直接使用JDBC的提交和回滚设置,依赖从数据源获得的连接来管理事务作用域
MANAGER:这个配置几乎什么也不做
这两种事务管理器都不需要设置任何属性,其实都是类型别名而已。
在MyBatis中有三种内建的数据源类型:
UNPOOLED:会在每次请求时打开和关闭连接,速度慢,性能表现依赖于选用的数据库
POOLED:使用数据库连接池组织JDBC连接对象,避免了创建新连接实例必须的初始化和认证时间,便于并发Web应用快速响应。
JNDI:能在如EJB或应用服务器等容器中使用
MyBatis可以通过设置databaseId属性,实现针对不同的数据库厂商执行不同语句的效果。
MyBatis在实际工作中,回加载匹配databaseId和不带databaseId的所有语句,若同时找到了相同的语句,就会将不带databaseId的语句舍弃。
例:
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
需要在核心配置文件中注册每一个映射器文件,注册方法有四种:
使用相对于类路径的资源引用
使用完全限定资源定位符
使用映射器接口类的全类名
使用包名
<!-- 使用完全限定资源定位符(URL) -->
<!-- 使用映射器接口实现类的完全限定类名 -->
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<package name="org.mybatis.builder"/>
映射器是MyBatis中最重要的部分!
可从数据库中查询结果,并根据具体配置实现自动的对象封装返回。例:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
参数详解:
属性
描述
id
命名空间中唯一的标识符,可以被用来引用整条语句
parameterType
方法的传入参数的属性类(别名或全类名),实际上MyBatis会通过TypeHandler自动推断出类型,因此默认为 unset
resultType
方法返回参数的类型,如果返回的是集合,就写泛型的类型(别名或全类名)
resultMap
对外部resultMap的命名引用(和resultType二者只能选择一个使用)
flushCache
设置为true后,会导致每次调用该语句都刷新一级、二级缓存(默认为 false)
useCache
设置为true后,会导致调用该语句后,结果会被保存到二级缓存中(默认为true)
timeout
驱动程序等待数据库返回结果的秒数
fetchSize
驱动程序每次批量返回结果的行数
statementType
可选STATEMENT, PREPARED, CALLABLE(默认为 PREPARED)
resultSetType
可选FORWARD_ONLY, SCROLL_SENSITIVE, SCROLL_INSENSITIVE, DEFAULT(即unset)
databaseId
数据库厂商标识
resultOrdered
设置为true后,会导致嵌套查询的内部结果不会产生对之前结果的引用,能节省内存(默认为false)
resultSets
为返回的结果集赋予名称,多个名称间用逗号隔开
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
数据变更语句,例:
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>
相比于select特殊的属性为:
属性
描述
useGeneratedKeys
仅适用于 insert 和 update,会调用getGeneratedKey()方法取出由数据库内部生成的主键(默认值为 false)
keyProperty
仅适用于 insert 和 update,指定能够唯一识别对象的属性,如果生成列不止一个就用逗号隔开
keyColumn
仅使用于 insert 和 update,设置生成键值在表中的列明,如果生成列不止一个就用逗号隔开
具体用法:
<insert id="insertAuthor" useGeneratedKeys="true"
keyProperty="id">
insert into Author (username,password,email,bio)
values (#{username},#{password},#{email},#{bio})
</insert>
该元素可用来定义可重用的代码片段,例:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </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>
也可以在sql内部调用sql,例:
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
占位符:
${ }:MyBatis进行字符串拼接,会有潜在的SQL注入风险
混合搭配小技巧:
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
resultMap可以将数据从resultSet中提取出来,并且支持类似自定义一样的封装。非常重要!!!
例:
<!-- 配置resultMap,并取名为userResultMap -->
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<!-- 调用resultMap,并以此为依据进行数据封装 -->
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
但是,很多时候,我们会写复杂的查询语句,此时 resultMap的配置也会变得较为繁琐。一个对象可能关联多个其他对象,或关联一个集合,等等。
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
constructor:用于在实例化类时,调用构造方法
id:一个ID结果,标记出来可以提高整体的性能
result:注入到字段或 JavaBean属性的普通结果
association:复杂的关联类型,内部可嵌套 resultMap
collection:复杂的集合类型,内部可以嵌套 resultMap
discriminator:根据结果值判断具体使用哪个resultMap
resultMap中可配置的属性
属性
描述
id
当前命名空间中的一个唯一标识,用于标识一个结果映射
type
类的别名或全类名
autoMapping
若设置为 true,则会开启自动映射(默认为 unset)
这两个元素是映射的基础
id 和 result 的一些属性
属性
描述
property
Java类中的属性名
column
数据库中的列名或是查询语句中列的别名
javaType
Java类的类别名或全类名(MyBatis通常能够自动推断出来)
jdbcType
JDBC类型,如果对象列是可修改的可为空的列,就必须给出
typeHandler
类型处理器
针对一些很少改变或者基本不变类或表,建议通过 constructor 进行创建。
MyBatis能通过某种方式定位到 Java类中相应的构造方法,并将结果注入其中。如果存在名称和类型相同的属性则可以省略 javaType
<constructor>
<idArg column="id" javaType="int" name="id" />
<arg column="age" javaType="_int" name="age" />
<arg column="username" javaType="String" name="username" />
</constructor>
constructor 的一些属性
属性
描述
column
数据库中的列名或是查询语句中列的别名
javaType
Java类的类别名或全类名(MyBatis通常能够自动推断出来)
jdbcType
JDBC类型,如果对象列是可修改的可为空的列,就必须给出
typeHandler
类型处理器
select
加载复杂类型属性的映射语句
resultMap
映射嵌套的结果集
name
构造方法形参的名字
关联元素能处理 “ has a ”的关系,如:一个用户对应一个博客,两个Java类内部存在关联。
MyBatis有两种加载关联的形式:
嵌套 select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型
嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集
association 的属性
属性
描述
property
Java类中的属性名
javaType
Java类的类别名或全类名(MyBatis通常能够自动推断出来)
jdbcType
JDBC类型,如果对象列是可修改的可为空的列,就必须给出
typeHandler
类型处理器
关联嵌套的Select查询
单独设置多个 select 查询语句,一个用来查询 A,一个用来查询 B ……
其他的所有属性都会被自动加载,只要它们的列名和属性名想匹配
但是,存在 ”N+1查询问题“:每一次执行一条单独的 SQL 语句获取一个列表,针对列表中的每一个记录,都会执行一个 select查询语句来加载详细信息。效率低!
嵌套select查询的属性
属性
描述
column
数据库中的列名或别名
select
加载复杂类型属性的语句ID,会从 column属性指定的列中检索数据,并传递给目标 select语句
fetchType
可选 lazy 或 eager
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
关联的嵌套结果映射
针对复杂的嵌套关联语句,我们可以选择连接查询。
关联嵌套的属性:
属性
描述
resultMap
引用的嵌套结果集,可以实现 resultMap的复用
columnPrefix
给表中的每列设置相同的额外的前缀,来避免重复
notNullColumn
指定非空列(指定后,MyBaits只有在该列非空情况下才会创建对象)
autoMapping
自动映射
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
注意:在嵌套查询中,id 元素非常重要,设置与否会严重影响性能!
此外,针对结果列名和映射列名不同,设置 columnPrefix:
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author"
resultMap="authorResult" />
<association property="coAuthor"
resultMap="authorResult"
columnPrefix="co_" />
</resultMap>
集合元素类似管理元素,针对 一个博客有多篇文章类型的问题。
同样,集合中也可以进行 嵌套 select查询和嵌套结果查询。
嵌套select查询
<resultMap id="blogResult" type="Blog">
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
ofType属性:指定泛型类型,即集合存储元素的类型
其余属性含义和 association中相同
嵌套结果查询
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>
再次强调 id元素对性能的重要性!
有时候一个数据库拆线呢可能会返回多个不同的结果集,因此可以使用鉴别器来实现针对不同的值采用不同的resultMap承接。
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultType="carResult">
<result property="doorCount" column="door_count" />
</case>
<case value="2" resultType="truckResult">
<result property="boxSize" column="box_size" />
<result property="extendedCab" column="extended_cab" />
</case>
<case value="3" resultType="vanResult">
<result property="powerSlidingDoor" column="power_sliding_door" />
</case>
<case value="4" resultType="suvResult">
<result property="allWheelDrive" column="all_wheel_drive" />
</case>
</discriminator>
</resultMap>
discriminator的属性:
属性
描述
column
数据库中的列名或列别名
javaType
Java类的类别名或全类名(MyBatis通常能够自动推断出来)
value
查出来待判断的值
resultMap
针对不同值适用的不同结果集
默认情况下MyBatis之开启本地缓存,即一级缓存,仅针对一个sqlSession中的数据进行缓存
要开启二级缓存需要手动在映射器中设置:
<cache/>
二级缓存开启后,
所有的select语句都会被缓存
只有发生insert, update, delete语句时才会刷新缓存
会使用LRU算法(最少使用算法)清除不必要的缓存
最多缓存1024个对象
对缓存的操作被视为 读/写操作,因此获取到的对象是不共享的
cache的属性:
属性
描述
eviction
配置清理缓存的算法,可选项有:LRU、FIFO、SOFT、WEAK
flushInterbal
设置刷新间隔
size
设置最大缓存数目
readOnly
设置只读属性。如果只读,则调用缓存时返回同一个对象,高效;如果可读可写,返回缓存对象的拷贝,安全但低效。
使用MyBatis最主要的接口就是SqlSession,我们可以通过这个接口来执行命令,获取映射器和管理事务。
SqlSession由SqlSessionFactory实例创建;SqlSessionFactory则由SqlSessionFactoryBuilder读取配置文件或注解创建。
String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
这里我们调用Resources工具类,从类路径、文件系统或者一个 web URL中加载资源文件。内部API 如下:
URL getResourceURL(String resource)
URL getResourceURL(ClassLoader loader, String resource)
InputStream getResourceAsStream(String resource)
InputStream getResourceAsStream(ClassLoader loader, String resource)
Properties getResourceAsProperties(String resource)
Properties getResourceAsProperties(ClassLoader loader, String resource)
Reader getResourceAsReader(String resource)
Reader getResourceAsReader(ClassLoader loader, String resource)
File getResourceAsFile(String resource)
File getResourceAsFile(ClassLoader loader, String resource)
InputStream getUrlAsStream(String urlString)
Reader getUrlAsReader(String urlString)
Properties getUrlAsProperties(String urlString)
Class classForName(String className)
SqlSessionFactoryBuilder 有五个 build() 方法
SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)
其中environment决定加载哪种环境,properties决定加载哪些属性,优先级最高。
Configuration类中包含了对所有配置的设置,并以 Java API 的形式展示出来:
DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);
SqlSessionFactory 中有六个方法创建 SqlSession 实例。
SqlSession openSession()
SqlSession openSession(boolean autoCommit) // 传 true将开启自动提交事务
SqlSession openSession(Connection connection) // 使用自己的 Connection 实例,MyBaits会根据 Connection来自动判断是否启用 autoCommit
SqlSession openSession(TransactionIsolationLevel level) // 设置事务隔离级别(NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType) // 设置执行器类别(SIMPLE, REUSE, BATCH)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
通过不同方法的选择,可以实现对事务处理、数据库连接、语句执行的设置。
默认的空参方法会创建具有如下特性的 SqlSession:
SqlSession包含所有执行语句、提交或回滚事务以及获取映射器的方法。
这些方法用来执行定义在SQL映射文件中的语句,具体参数可以是原始类型、JavaBean、POJO或Map
<T> T selectOne(String statement, Object parameter) // 若返回多个对象就会报错,若未查到则返回 null
<E> List<E> selectList(String statement, Object parameter) // 返回列表,可承接多个对象
<T> Cursor<T> selectCursor(String statement, Object parameter) // 返回游标,借助迭代器实现懒加载
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey) // 返回字典,对象的一个属性作为key,对象作为value
int insert(String statement, Object parameter) // 返回影响行数
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
分页查询方法:
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
关于 RowBounds类,它是不可变类:
int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);
当我们将 MyBatis的执行器类型设置为 BATCH时,可以使用以下方法执行缓存在 JDBC驱动类中的批量更新语句。
List<BatchResult> flushStatements()
在MyBatis中定义一个接口和同名的映射文件,通过反射的方法来操作数据库。
public interface AuthorMapper {
Author selectAuthor(int id);
List<Author> selectAuthors();
@MapKey("id")
Map<Integer, Author> selectAuthors();
int insertAuthor(Author author);
int updateAuthor(Author author);
int deleteAuthor(int id);
}
映射器接口不需要实现任何方法或继承自任何类,且不能在两个具有继承关系的接口中拥有相同的方法签名
可以选择给接口中的方法配置注解来替代或者方便映射文件的设置
注解
使用对象
XML等价形式
描述
类
为给定的命名空间配置缓存,属性同标签类似
N/A
指定参数值或占位符,属性同标签
类
引用另一个命名空间的缓存配置
方法
收集一组结果来传给构造方法,属性:Value数组
N/A
|
表示构造方法的参数,是一个布尔值,表示该属性是否用于唯一标识
方法
根据不同取值决定使用何种映射集
N/A
表示某个值和对应情况下的映射
方法
定义一组映射结果集
N/A
|
在属性和字段之间设置单个结果映射
N/A
复杂类型的单个属性映射
N/A
复杂类型的集合属性映射
方法
无
供返回值为Map的方法使用,使得注释的属性成为key,将对象转化为Map
方法
映射语句的属性
可设置大部分的开关和配置项
@Insert | @Update | @Delete | @Select
方法
| | |
分别代表将被执行的SQL语句
@InsertProvider | @UpdateProvider | @DeleteProvider | @SelectProvider
方法
| | |
分别代表构建的动态SQL语句
参数
无
自定义每个参数的名字
方法
查询主键
方法
无
使用该注解后就能调用flushStatement()方法
示例:
@Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
int insertTable3(Name name);
@Insert("insert into table2 (name) values(#{name})")
@SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
int insertTable2(Name name);
@Flush
List<BatchResult> flush();
@Results(id = "userResult", value = {
@Result(property = "id", column = "uid", id = true),
@Result(property = "firstName", column = "first_name"),
@Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);
@Results(id = "companyResults")
@ConstructorArgs({
@Arg(column = "cid", javaType = Integer.class, id = true),
@Arg(column = "name", javaType = String.class)
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);
class UserSqlBuilder {
public static String buildGetUsersByName(final String name) {
return new SQL(){{
SELECT("*");
FROM("users");
if (name != null) {
WHERE("name like #{value} || '%'");
}
ORDER_BY("id");
}}.toString();
}
}
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(
@Param("name") String name, @Param("orderByColumn") String orderByColumn);
class UserSqlBuilder {
// 如果不使用 @Param,就应该定义与 mapper 方法相同的参数
public static String buildGetUsersByName(
final String name, final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
// 如果使用 @Param,就可以只定义需要使用的参数
public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
}
动态SQL是MyBatis的强大特性,能够方便SQL语句的拼接
主要标签:
提供可选的查找功能,如果传入了某个参数,就将该参数作为查找标准之一。
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
类似Java中的 switch, case, default
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
解决 where语句中 and拼接 和 update语句中 逗号拼接的问题
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
元素会在子元素返回内容的情况下才插入 WHERE子句,并且若子句以 AND或OR开头也会根据情况删除或保留
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
元素会动态地在首行插入SET关键字,并且会根据情况删掉或保留逗号
此外,也可以自定义类似的元素标签
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<trim prefix="SET" suffixOverrides=",">
...
</trim>
可以动态的遍历集合,在构造条件或插入语句时特别有用
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
手机扫一扫
移动阅读更方便
你可能感兴趣的文章