接上一篇Mybatis学习(一):基础概念和简单自定义持久层框架demo
ORM(Object/Relation Mapping):对象—关系映射的缩写。
ORM完成面向对象的编程语言到数据库的映射。ORM把关系数据库包装成面向对象的模型。采用orm框架后,应用程序不再直接访问数据库,而是用面向对象的方式操作持久化对象,orm框架本身把这些操作转变成数据库底层SQL操作。
Mybatis是一款优秀的基于ORM的半自动化轻量级持久层框架,它支持定制化SQL、存储过程和高级映射。Mybatis可以用简单的xml或者注解来配置和映射原生类型、接口和实体类。
MyBatis官方地址:https://mybatis.org/mybatis-3/zh/index.html
新建一个maven项目
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
public class User {
private int id;
private String name;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
<?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">
>
>
<?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">
>
<!--environments:运行环境-->
<!--引入映射配置文件-->
>
注意: 驱动class注意mysql版本的区别(8.0以上是"com.mysql.cj.jdbc.Driver")
@org.junit.Test
public void test1() throws IOException {
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
//在进行增删改操作时,要手动提交事务
//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:delete
List<User> users = sqlSession.selectList("com.learn.dao.UserDao.findAll");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
前边的例子已经展示过查询了
映射文件UserMapper.xml中新增插入方法:
<insert id="add" parameterType="com.learn.pojo.User">
insert into user values(#{id},#{name},#{password})
</insert>
编写测试方法:
@org.junit.Test
public void test2() throws IOException {
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:delete
User user = new User();
user.setId(2);
user.setName("lisi");
user.setPassword("123456");
int insert = sqlSession.insert("com.learn.dao.UserDao.add", user);
System.out.println(insert);
//手动提交事务
sqlSession.commit();
sqlSession.close();
}
映射文件UserMapper.xml中新增修改方法:
<update id="update" parameterType="com.learn.pojo.User">
update user set name=#{name},password=#{password} where id=#{id}
</update>
编写测试方法:
@org.junit.Test
public void test3() throws IOException {
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:delete
User user = new User();
user.setId(2);
user.setName("lisi2");
user.setPassword("123456");
int update = sqlSession.update("com.learn.dao.UserDao.update", user);
//手动提交事务
sqlSession.commit();
sqlSession.close();
System.out.println(update);
}
映射文件UserMapper.xml中新增删除方法:
<delete id="delete" parameterType="int">
delete from user where id=#{id}
</delete>
编写测试方法:
@org.junit.Test
public void test4() throws IOException {
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:delete
int delete = sqlSession.delete("com.learn.dao.UserDao.delete", 2);
//手动提交事务
sqlSession.commit();
sqlSession.close();
System.out.println(delete);
}
public interface UserDao {
List<User> findAll();
}
public class UserDaoImpl implements UserDao {
@Override
public List<User> findAll() throws Exception {
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
//在进行增删改操作时,要手动提交事务
//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:delete
List<User> users = sqlSession.selectList("com.learn.dao.UserDao.findAll");
sqlSession.close();
return users;
}
}
@org.junit.Test
public void test5() throws Exception {
UserDao userDao = new UserDaoImpl();
List<User> userList = userDao.findAll();
System.out.println(userList);
}
public interface UserMapper{
List<User> findAll();
}
<?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="com.learn.dao.UserMapper">
<!--namespace : 名称空间:与id组成sql的唯一标识
resultType: 表明返回值类型-->
<!--查询用户-->
<select id="findAll" resultType="com.learn.pojo.User">
select * from user
</select>
<insert id="add" parameterType="com.learn.pojo.User">
insert into user values(#{id},#{name},#{password})
</insert>
<update id="update" parameterType="com.learn.pojo.User">
update user set name=#{name},password=#{password} where id=#{id}
</update>
<delete id="delete" parameterType="int">
delete from user where id=#{id}
</delete>
</mapper>
@org.junit.Test
public void test6() throws Exception {
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.findAll();
sqlSession.close();
System.out.println(users);
}
注意:
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
例子:
<properties resource="config.properties">
<property name="username" value="dev"/>
<property name="password" value="dev"/>
</properties>
<!--environments:运行环境-->
<environments default="dev">
<environment id="dev">
<!--当前事务交由JDBC进行管理-->
<transactionManager type="JDBC"/>
<!--当前使用mybatis提供的连接池-->
<dataSource type="POOLED">
<!--<property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
<property name="driver" value="${driver}"/>
<property name="url" value="jdbc:mysql:///mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false"/>
<!--<property name="username" value="root"/>-->
<!--<property name="password" value="xijian"/>-->
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
说明:
public SqlSessionFactory build(Reader reader, Properties properties) {
//。。。。。
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
//。。。。。
}
注意:
如果一个属性在不只一个地方进行了配置,那么MyBatis 将按照下面的顺序来加载:
从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值。例如:
如果属性 ‘username’ 没有被配置,‘username’ 属性的值将为 ‘ut_user’。
<properties resource="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>
一个配置完整的 settings 元素的示例如下:
<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官方文档
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
举个栗子:
<typeAliases>
<typeAlias alias="user" type="com.learn.pojo.User"/>
</typeAliases>
也可以指定一个包下所有的Bean类
<typeAliases>
<!--<typeAlias alias="user" type="com.learn.pojo.User"/>-->
<package name="com.learn.pojo"/>
</typeAliases>
com.learn.pojo
包下所有的bean类如果没有注解,那么默认使用首字母小写的非限定类名来作为它的别名。有注解,则别名为其注解值。
@Alias("user")
public class User {
//。。。。
}
MyBatis在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类。
MyBatis中有很多默认支持的类型处理器,具体参考:官方文档
我们可以重写已有的类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。
具体做法为:
实现 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);
}
}
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
注意:
在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType=“String”)MyBatis 可以得知该类型处理器处理的 Java 类型;在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType=“VARCHAR”)指定与其关联的 JDBC 类型列表。
在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表;在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。
==上述的1,2两种配置方式,1的优先级高于2。==
我们可以通过指定包名的方式让 MyBatis 帮我们查找类型处理器
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>
注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。
每次 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的配置文件中增加对应配置
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
objectFactory 元素体中定义的属性会被传递给 setProperties
方法
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
@Intercepts({@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
@Override
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
在Mybatis配置文件中新增配置:
<plugins>
<plugin interceptor="com.learn.interceptor.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
示例:
<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 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:
driver.encoding=UTF8
这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8 的 encoding 属性给数据库驱动。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:
和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如:env.encoding=UTF8
这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8 的 encoding 属性。
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>
在提供了属性别名时,databaseIdProvider 的 DB_VENDOR 实现会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为 “null”。 在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。
我们可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider:
public interface DatabaseIdProvider {
default void setProperties(Properties p) { // 从 3.5.2 开始,该方法为默认方法
// 空实现
}
String getDatabaseId(DataSource dataSource) throws SQLException;
}
告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.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>
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
Select 元素的属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap | 对外部 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 | 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
数据变更语句 insert,update 和 delete 的实现非常接近:
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
Insert, Update, Delete 元素的属性:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType | 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys | 仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 |
keyProperty | (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | 仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
databaseId | 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
注意:
<insert id="insertAuthor">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>
selectKey 元素的属性:
属性 | 描述 |
---|---|
keyProperty | selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | 返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
resultType | 结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。 |
order | 可以设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它首先会生成主键,设置 keyProperty 再执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。 |
statementType | 和前面一样,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 类型的映射语句,分别代表 Statement, PreparedStatement 和 CallableStatement 类型。 |
这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。
<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>
也可以在 include 元素的 refid 属性或内部语句中使用属性值,例如:
<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 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:ORDER BY ${columnName}
。
提示:
用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。
在没有显式的指定 resultMap时,可以使用resultType 属性指定,将所有的列映射到 HashMap的键上。
当resultType 属性指定到JavaBean时,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。
我们也可以显式使用外部的 resultMap,然后在引用它的语句中设置 resultMap 属性就行了。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
注意:
高级结果映射 请跳转 官方文档—结果映射
在简单的场景下,MyBatis 可以为你自动映射查询结果。但如果遇到复杂的场景,你需要构建一个结果映射。我们可以混合使用这两种策略。
例如:
<resultMap id="userResultMap" type="User">
<result property="password" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select
user_id as "id",
user_name as "userName",
hashed_password
from some_table
where id = #{id}
</select>
例子中,id 和 userName 列将被自动映射,hashed_password 列将根据配置进行映射。
有三种自动映射等级:
NONE
- 禁用自动映射。仅对手动映射的属性进行映射。PARTIAL
- 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射FULL
- 自动映射所有属性。PARTIAL
,这是有原因的。当对连接查询的结果使用 FULL 时,连接查询会在同一行中获取多个不同实体的数据,因此可能导致非预期的映射。无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置 autoMapping
属性来为指定的结果映射设置启用/禁用自动映射。
<resultMap id="userResultMap" type="User" autoMapping="false">
<result property="password" column="hashed_password"/>
</resultMap>
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
这个简单语句的效果如下:
提示:
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
提示:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 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>
在 where 后的条件语句中根据多个参数动态判断时,使用多个if标签完成。
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<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>
在上面的例子中:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就使用featured = 1
的条件。
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},
<if test="password != null">password=#{password},
<if test="email != null">email=#{email},
<if test="bio != null">bio=#{bio}
</set>
where id=#{id}
</update>
<trim prefix="SET" suffixOverrides=",">
...
</trim>
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<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>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。
提示:
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。
bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
如果配置了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 从 3.2 版本开始支持插入脚本语言,这允许你插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。
可以通过实现以下接口来插入一种语言:
public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
实现自定义语言驱动后,你就可以在 mybatis-config.xml 文件中将它设置为默认语言:
<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
或者也可以使用 lang 属性为特定的语句指定语言:
<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>
或者在mapper 接口上添加 @Lang 注解:
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}
提示:
前面看到的所有 xml 标签都由默认 MyBatis 语言提供,而它由语言驱动 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver
(别名为xml
)所提供。
本篇笔者描述了一下MyBatis的demo入门应用,配置文件属性说明及动态SQL。
下一篇笔者将讲解传统XML配置开发、注解开发、MyBatis缓存、MyBatis插件。