基本使用
-
引入 jar 文件,jdbc 的 jar 和 mybatis 的 jar,如果需要使用 mybatis 的高级功能可以再添加相关的依赖
-
根据全局配置文件创建一个 SqlSessionFactory 对象
-
sql 映射文件;配置了每一个 sql,以及 sql 的规则封装等
-
将 sql 映射文件注册中在全局配置中
-
代码编写/接口和非接口两种使用方式
<?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="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/mm?characterEncoding=utf-8" />
<property name="username" value="tmaize" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="User.xml" />
</mappers>
</configuration>
非接口式编写
<!-- 命名空间,为sql语句划定范围,同时减少输入,在接口式编写中用于指定mapper位置 -->
<mapper namespace="namespace1">
<select id="findUserById" parameterType="int" resultMap="userResultMap">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
try{
//sqlSession非线程安全,最好在方法体内定义,传入false可以设置为非自动提交
SqlSession sqlSession = sqlMapper.openSession();
// ...事务相关的代码
// sqlSession.commit();
User user = sqlSession.selectOne("namespace1.findUserById", 1);
}catch(){
//回滚
}finally(){
//每次使用结束最好关闭它
sqlSession.close();
}
接口式编写
底层使用代理来实现,MyBatis 会为接口自动创建一个代理对象类执行接口里面的方法,好处在于能够规范传递的参数类型。同时 namespace 和 Mapper 的全类名对应,id 和方法名对应
UserMapper.xml,在主配置文件中加入 UserMapper.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="dao.UserDao">
<select id="findUserById" parameterType="int" resultType="entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
UserDao.java
import entity.User;
public interface UserDao {
//方法名和UserMapper.xml中语句的id一样
public User findUserById(int id);
}
测试
//动态代理关键在于不用写dao实现了
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findUserById(1);
System.out.println(user.toString());
注意:sqlSessionFactory.openSession()默认是 false,即不自动提交更改,便于事务的回滚,在测试的时候可以 sqlSessionFactory.openSession(true),即自动提交,不需要手动 commit
主要类
-
SqlSessionFactoryBuilder
这个类可以被实例化,使用和丢弃。一旦你创建了 SqlSessionFactory 后,这个类就不需要存在了。因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是本地方法变量)。
你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好的方式是不需要保持它一直存在来保证所有 XML 解析资源,因为还有更重要的事情要做。
-
SqlSessionFactory
一旦被创建, SqlSessionFactory 应该在你的应用执行期间都存在。没有理由来处理或重新创建它。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。 这样的操作将被视为是非常糟糕的。因此 SqlSessionFactory 的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。然而这两种方法都不认为是最佳实践。 这样的话,你可以考虑依赖注入容器,比如 Google Guice 或 Spring。这样的框架允许你创建支持程序来管理单例 SqlSessionFactory 的生命周期。
-
SqlSession 每个线程都应该有它自己的 SqlSession 实例。 SqlSession 的实例不能被共享,也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个类的静态字段甚至是实例字段中。也绝不能将 SqlSession 实例的引用放在任何类型的管理范围中,比如 Serlvet 架构中的 HttpSession。如果你现在正用任意的 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围内。换句话说,基于收到的 HTTP 请求,你可以打开了一个 SqlSession,然后返回响应,就可以关闭它了。 关闭 Session 很重要,你应该确保使用 finally 块来关闭它。
配置显示 SQL 语句
Mybatis 内置的日志工厂提供日志功能,需要导入相关的 jar 包,具体的日志实现有以下几种方式:
-
SLF4J
-
Apache Commons Logging
-
Log4j 2
-
Log4j
-
JDK logging
具体选择哪个日志实现由 MyBatis 的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。 如果一个都未找到,日志功能就会被禁用。
不少应用服务器的 classpath 中已经包含 Commons Logging,如 Tomcat 和 WebShpere, 所以 MyBatis 会把它作为具体的日志实现。
如果使用其他的最好手动指定需要在配置文件中指定使用那种日志工具
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
接着创建 log4j.properties
log4j.rootLogger=DEBUG, Console
# Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
全局配置文件
MyBatis 的 XML 配置文件包含了影响 MyBatis 行为甚深的设置和属性信息。 XML 文档的高层级结构如下:
注意:在进行配置时,标签的顺序必须和下面的顺序一致,否则会报错
- configuration 配置
- properties 属性
- settings 设置
- typeAliases 类型命名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- environment 环境变量
- mappers sql 语句映射文件
properties 引用外部配置
在配置文件里面可以通过${key}的方式来得到外部 properties 对应的 value
<properties resource="org/mybatis/example/config.properties">
<!-- 覆盖或者重新定义 -->
<property name="username" value="dev_user"/>
</properties>
Settings
这些是极其重要的调整,它们会修改 MyBatis 在运行时的行为方式。
<settings>
<setting name="cacheEnabled" value="true"/>
<!-- ... -->
</settings>
typeAliases 起别名
类型别名是为 Java 类型命名一个短的名字。它只和 XML 配置有关,只用来减少类完全限定名的多余部分对于普通的 Java 类型,有许多内建的类型别名。它们都是大小写不敏感的,由于重载的名字,要注意原生类型的特殊处理。
为类自定义别名
<typeAliases>
<typeAlias alias="User" type="net.tmaize.mybatis.entity.User"/>
<!-- 为某个包下面的所有类起别名,默认名是类名小写 -->
<!-- 需要自定义名字可以在类上添加@Alias注解指定 -->
<package name="xx.xx.xx"></package>
</typeAliases>
起了别名后下面两个是等价的
<select id="findUserById" parameterType="int" resultType="net.tmaize.mybatis.entity.User">
<select id="findUserById" parameterType="int" resultType="User">
typeHandlers
无论是 MyBatis 在预处理语句中设置一个参数,还是从结果集中取出一个值时,类型处理器被用来将获取的值以合适的方式转换成 Java 类型。下面这个表格描述了默认的类型处理器。
MyBatis3.4 以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的
最重要的是你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型
自定义累类型处理器
public class ExampleTypeHandler implements TypeHandler{}
添加到配置文件中
<typeHandlers>
<typeHandler javaType="String" jdbcType="VARCHAR" handler="xx.ExampleTypeHandler"/>
</typeHandlers>
objectFactory
MyBatis 每次创建结果对象新的实例时,它使用一个 ObjectFactory 实例来完成。如果参数映射存在,默认的 ObjectFactory 不比使用默认构造方法或带参数的构造方法实例化目标类做的工作多。 如果你想重写默认的 ObjectFactory,你可以创建你自己的。
ObjectFactory 接口很简单。它包含两个创建用的方法,一个是处理默认构造方法的,另外一个是处理带参数构造方法的。最终, setProperties 方法可以被用来配置 ObjectFactory。在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
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);
}
}
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
plugins
MyBatis 允许你在某一点拦截已映射语句执行的调用。默认情况下, MyBatis 允许使用插件来拦截方法调用
environments 多环境
MyBatis 可以配置多种环境。这会帮助你将 SQL 映射应用于多种数据库之中。例如,你也许为开发要设置不同的配置,测试和生产环境。或者你可能有多种生产级数据库却共享相同的模式,所以你会想对不同数据库使用相同的 SQL 映射。
一个很重要的问题要记得:你可以配置多种环境,但你只能为每个 SqlSessionFactory 实例选择一个
每个数据库对应一个 SqlSessionFactory
<environments default=" development">
<environment id="development">
<!-- ... -->
</environment>
<environment id="test">
<!-- ... -->
</environment>
</environments>
如果环境被忽略,那么默认环境将会被加载
// 指定配置中的环境id
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,environment,properties);
// 使用默认环境
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);
environment
environment>transactionManager
在 MyBatis 中有两种事务管理器类型type="[JDBC|MANAGED]"
JDBC – 这个配置直接简单使用了 JDBC 的提交和回滚设置。它依赖于从数据源得到的连接来管理事务范围。
MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接。而它会让容器来管理事务的整个生命周期(比如 Spring 或 JEE 应用服务器的上下文)。
environment>dataSource
dataSource 元素使用基本的 JDBC 数据源接口来配置 JDBC 连接对象的资源
有三种内建的数据源类型:
-
UNPOOLED
这个数据源的实现是每次被请求时简单打开和关闭连接。它有一点慢,这是对简单应用程序的一个很好的选择,因为它不需要及时的可用连接。不同的数据库对这个的表现也是不一样的,所以对某些数据库来说配置数据源并不重要,这个配置也是闲置的。
使用 driver,url,username,password,defaultTransactionIsolationLevel 来进行主要配置
作为可选项,你可以传递数据库驱动的属性。属性的前缀是以“driver.”开头的,例如:driver.encoding=UTF8 它是通过 DriverManager.getConnection(url,driverProperties)方法传递给数据库驱动。
-
POOLED
这是 JDBC 连接对象的数据源连接池的实现,用来避免创建新的连接实例时必要的初始连接和认证时间。这是一种当前 Web 应用程序用来快速响应请求很流行的方法。
除了上述( UNPOOLED)的属性之外,还有很多属性可以用来配置 POOLED 数据源:
比如:poolMaximumActiveConnections,poolMaximumIdleConnections,poolMaximumCheckoutTime…一般用默认值就可以了
-
JNDI
这个数据源的实现是为了使用如 Spring 或应用服务器这类的容器,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引
这个数据源配置只需要两个属性:initial_context,这个属性用来从初始上下文中寻找环境。data_source,这是引用数据源实例位置的上下文的路径
一个常用的配置
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/mm?characterEncoding=utf-8" />
<property name="username" value="tmaize" />
<property name="password" value="123456" />
</dataSource>
</environment>
mappers
告诉 MyBatis 到哪里去找到我们定义的 SQL 映射语句配置文件
resource 引用类路径下的文件,url 引用网络路径或者磁盘路径下的文件,class 引用接口 (配置文件和接口同名且在同一个目录下 ,或者接口的每个方法上有 mybatis 的注解)
<mappers>
<!-- 位置任意 -->
<mapper resource="xxx.UserMapper.xml" />
<!-- 批量添加,要求SQL映射文件名必须和接口名相同并且在同一目录下,不能分开还有啥意思。不过 在和spring整合时支持配置mapperLocations来分离-->
<package name="xx.xx"><package>
</mappers>
databaseIdProvider
根据不同的数据库执行不同的 sql 语句
sql 语句匹配规则如下:
-
如果没有配置 databaseIdProvider 标签,那么 databaseId=null
-
如果配置了 databaseIdProvider 标签,使用标签配置的 name 去匹配数据库信息,匹配上设置 databaseId=配置指定的值,否则依旧为 null
-
如果 databaseId 不为 null,他只会找到配置 databaseId 的 sql 语句
-
MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句, 则后者会被舍弃。
<!-- type="VENDOR"固定,根据连接返回的信息的数数据库厂商 -->
<databaseIdProvider type="VENDOR">
<!-- 为不同的数据库厂商起别名,value为别名 -->
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
在 sql 映射文件中
<select id="findUserById" parameterType="int" resultType="entity.User" databaseId="mysql">
</select>
<select id="findUserById" parameterType="int" resultType="entity.User" databaseId="mysql">
</select>
SQL 映射文件配置
配置 SQL 语句…
SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
-
cache – 配置给定命名空间的缓存
-
cache-ref – 从其他命名空间引用缓存配置
-
resultMap – 最复杂,也是最有力量的元素,用来描述如何从数据库结果集中来加 载你的对象
-
parameterMap – 已经被废弃…
-
sql – 可以重用的 SQL 块,也可以被其他语句引用
-
insert – 映射插入语句
-
update – 映射更新语句
-
delete – 映射删除语句
-
select – 映射查询语句
<mapper namespace="namespace1">
<select id="findUserById" parameterType="int" resultType="entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
sql 操作
查询语句是使用 MyBatis 时最常用的元素之一
下面的语句被称作 selectPerson,使用一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值是列对应的值。
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
select 元素有很多属性允许你配置,来决定每条语句的作用细节
<select
id="selectPerson" 在命名空间中唯一的标识符,可以被用来引用这条语句
parameterType="int" 传入这条语句的参数类的完全限定名或别名
parameterMap="deprecated" 废弃
resultType="hashmap" 返回的期望类型的类的完全限定名或别名
resultMap="personResultMap" 命名引用外部的 resultMap,解决了许多复杂映射的情形
flushCache="false" 每次调用是否清空缓存
useCache="true" 是否缓存这条语句的结果,默认值: true。
timeout="10000" 超过等待时间抛出异常,一般不设置
fetchSize="256" 暗示驱动程序每次批量返回的结果行数 一般不设置
statementType="PREPARED" 让MyBatis使用 Statement,PreparedStatement 还是CallableStatement
resultSetType="FORWARD_ONLY">
insert, update and delete 这几个可以归为一类,因为它们不需要返回特殊数据
所以它们的接口发方法可以直接定义下面记几种类型的返回值 Integer,Long ,Boolean(>0 为 true)
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
<update
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
参数传递
-
单个参数
mybatis 不会做特殊处理,#{名字随意}取出
注意单个字符串使用${_parameter},或者使用${value}
-
多个参数
任意多个参数,都会被 MyBatis 重新包装成一个 Map 传入。
Map 的 key 是 param1 , param2…,值就是参数的值,但是这样在 sql 中通过#{param1}取出不太直观,又有了下面的方法
使用@Param 参数来指定 key
// #{id},#{type.id};来获取数据 public boolean updateUserTypeId(@Param("id") Integer id,@Param("type") Type type);
-
POJO
当这些参数属于我们业务 POJO 时,我们直接传递 POJO
-
Map
我们也可以封装多个参数为 map,直接传递,通过自己设置的 key,在#{key}中取出
-
Collection(List、Set)和数组
也是会自动进行特殊处理,把 list,数组封装在 map 中
未进行@Param 时,key 不是 paramaN,key 分别为 collection,list,array,比如#{list[0]}
如果要传入的多个参数没有对应的业务模型(POJO),但是又要经常使用,为了方便推荐编写一个 TO(Transfer Object)数据传输对象
在使用接口开发时,标签上的 parameterType 可以不写,不过为了规范还是要写上 parameterType,同时建议使用接口开发,来规范化传入的参数类型
#{}和¥{}
默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并以它为背景设置安全的值(比如?)。这样做很安全,很迅速也是首选做法,它是通过 PreparedStatement 添加参数。
有时你只是想直接在 SQL 语句中插入一个不改变的字符串,应该使用${},这是简单的字符串拼接,比如在 order by 时不支持?占位符,可以通过这个来解决
参数处理
参数位置支持的属性
javaType、 jdbcType、 mode、 numericScale、resultMap、 typeHandler、 jdbcTypeName、 expression
比如
#{id ,javaType=int,jdbcType=NUMERIC}
jdbcType 通常需要在某种特定的条件下设置,在数据为 null 的时候,有些数据库不能识别 mybatis 对 null 的默认处理,比如 Oracle
mybatis 对所有的 null 都映射的是 JdbcType 里面的 Other
public enum JdbcType {
NULL(Types.NULL),
OTHER(Types.OTHER),
// ...
}
可以设置#{jdbcType=NULL}
或者在全局配置文件中设置
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
返回值
resultType,如果要返回集合,类型要填写集合里面元素的类型
当结果有一行的时候返回对象,当有多行的时候返回 List<对象>,但是 resultType 都是对象对象>
例如返回 hashmap,key 为列名,value 为对应的行值
当为一行结果时候,实际得到的是 Map<String,Object>
当为多行结果时候,实际得到的是 List<Map<String,Object»
在执行 insert, update and delete 时可以直接定义下面记几种类型的返回值 Integer,Long ,Boolean(>0 为 true),如果使用接口开发,在接口中方法的返回类型指定即可,比如:
public boolean addUser(User user);
ResultMap 是一个很重要的特性当你不知道返回的数据类型的时候,你可以指定返回 hashmap 类型,所有列被自动映射到 HashMap 的键上
HashMap<String, String> map = sqlSession.selectOne("namespace1.hashmap_id",1);
映射到 JavaBean
<select id="findUserById" parameterType="int" resultType="entity.User">
SELECT * FROM user WHERE id = #{id}
</select>
在 resultType 指定为一个实体类的时候,MyBatis 也是会在幕后自动创建一个 ResultMap,然后基于属性名/get 来映射列到 JavaBean 的属性上,如果类名和属性名不一致的的话可以在 sql 语句中为列名起别名
MyBatis 还提供了另外一种方式来解决列名和属性名不匹配的问题
<resultMap id="userResultMap" type="entity.User">
<id property="id" column="id" />
<result property="name" column="u_name" />
<!-- 列名和属性名一致的时候可以不写这条result -->
<result property="address" column="address" />
</resultMap>
<select id="findUserById" parameterType="int" resultMap="userResultMap">
SELECT * FROM user WHERE id = #{id}
</select>
级联映射
同时 resultMap 还支持级联属性封装,比如某些属性封装成实体类的某个对象属性
<resultMap type="xx.user" id="user_map">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="address" column="address" />
<association property="userType" javaType="xx.userType">
<id column="userType_id" property="id" />
<result column="userType_name" property="name" />
</association>
</resultMap>
或者使用一对多的级联映射,和 association 的使用方法相似,比如查询某个部门信息时候,顺便查出部门下的所有员工
<resultMap type="entity.TypeVo" id="collTestMap">
<id property="id" column="id" />
<result property="name" column="name"/>
<collection property="users" ofType="entity.User">
<id property="id" column="u_id"/>
<result property="name" column="u_name"/>
</collection>
</resultMap>
<select id="typeAndUserByTypeId" parameterType="int" resultMap="collTestMap">
SELECT type.* ,user.id u_id,user.name u_name
FROM type LEFT JOIN user ON user.type_id=type.id
WHERE type.id=#{id}
</select>
<!-- Very Complex Result Map -->
<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>
关联的嵌套查询,延迟加载
resultMap 可实现高级映射(使用 association、collection 实现一对一及一对多映射),association、collection 具备延迟加载功能。
在进行级联查询的时候,在某个实体类内的某个属性在进行使用的时候再去加载
在全局配置文件中添加如下配置或者在标签中设置 fectht
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 某个属性使用时其余属性全部加载,新版本默认为false -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<resultMap type="entity.User" id="lazyloadingTest">
<id property="id" column="id" />
<result property="name" column="name"/>
<association property="type" javaType="entity.Type" select="findTypeById" column="type_id">
</association>
</resultMap>
<select id="findUserById" parameterType="int" resultMap="lazyloadingTest">
SELECT * FROM user WHERE id = #{id}
</select>
<select id="findTypeById" parameterType="int" resultType="entity.Type">
SELECT * FROM type WHERE id = #{id}
</select>
User user = userDao.findUserById(5);
System.out.println(user.getId());
System.out.println(user.getName());
// 开启懒加载时,再发送一条语句
System.out.println(user.getType());
自增主键的获取
mysql 支持自增主键,以及值的获取,mybatis 也是利用 statement.getGenreatedkeys()
useGeneratedKeys 是否使用自增主键获取主键值策略
keyProperty 把添加后生成的主键映射到实体类的哪个属性上
keyProperty
<insert id="addUser" parameterType="entity.User" useGeneratedKeys="true" keyProperty="id">
Oracle 不支持自增,Oracle 使用序列来模拟自增,每次输入的数据的主键都是从序列中拿到的值
<insert id="addUser" parameterType="entity.User">
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
SELECT EMPLOYEE_SQL.nextval from dual
</selectKey>
INSERT INTO user(id,name,address) VALUES(#{id},#{name},#{address})
</insert>
动态 SQL
MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。 如果你有使用 JDBC 或其他 相似框架的经验,你就明白条件地串联 SQL 字符串在一起是多么的痛苦,确保不能忘了空 格或在列表的最后省略逗号。动态 SQL 可以彻底处理这种痛苦。
通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语 言来改进这种情形,这种语言可以被用在任意映射的 SQL 语句中。 动态 SQL 元素和使用 JSTL 或其他相似的基于 XML 的文本处理器相似。在 MyBatis 之 前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半 的元素就能工作了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。
<select id="findActiveBlogWithTitleLike" parameterType="Blog" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
</select>
常用标签
-
if
if 标签里面的 test 为 true,则 if 标签包含的内容将会拼接到 sql 语句中
-
where
去掉语句中第一个多余的 and 和 or,不喜欢用可以使用 1=1 (and xx)..来替代
<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>
-
choose(when,otherwise)
分支选择,带了 break 的 switch-case
类似于
switch case: a xxx break; case: b xxx break; default: xxx
<where> <choose> <when test="id!=null"> id=#{id} </when> <when test="lastName!=null"> last_name like #{lastName} </when> <when test="email!=null"> email=#{email} </when> <otherwise> gender=0 </otherwise> </choose> </where>
-
set
比如下面的语句,和 where 多条件一样容易多一个 and 这里可能会多一个逗号,所以出现了 set 标签
<update id="updateUser">
update user set
<if test="name!=null">
name=#{name},
</if>
<if test="birthday!=null">
birthday=#{birthday},
</if>
<if test="address!=null">
ddress=#{address}
</if>
where id = #{id}
</update>
有可能会多出一个逗号,应使用 set 标签
<update id="updateOne" parameterType="user">
update user
<set>
<if test="name!=null">
name=#{name},
</if>
<if test="birthday!=null">
birthday=#{birthday},
</if>
<if test="address!=null">
address=#{address}
</if>
</set>
where id = #{id}
</update>
-
trim
自定义字符串截取的规则,用 trim 标签也可以实现 set 和 where 标签的效果
<trim prefix="添加前缀" prefixOverrides="去掉前缀" suffix="添加后缀" suffixOverrides="去掉后缀"> <trim prefix="where" prefixOverrides="and"> <if test="name!=null"> and name=#{name} </if> <if test="age!=0"> and age = #{age} </if> </trim>
-
foreach
将遍历出的元素赋值给指定变量,然后用#{变量名}就可以取出 list 中每个元素的内容
<foreach collection="要遍历的元素" item="每一项的变量名" index="第几个的变量名或者map的key" separator="分隔符" open="添加前缀" close="添加后缀"> </foreach> List<String> li = new ArrayList<>(); li.add("hello"); li.add("lisi"); <foreach collection="list" item="item" index="index" separator="," open="(" close=")"> #{index}=#{item} </foreach> 生成的是 ( ?=? , ?=? ) 把#换成$ ( 0=hello , 1=lisi )
批量保存
insert into table value (…),(…) …;
或者在 jdbc 连接 url 上加上允许一次执行以分号划分的多条语句
jdbc:mysql://127.0.0.1:3306/mm?characterEncoding=utf-8&allowMultiQueries=true
-
bind
可以将 OGNL 表达式的值绑定到一个变量中,方便后来引用这个值
<select id="test"> <bind name="_lastname" value="'%'+lastname+'%'"/> select * from user where name like #{_lastname} </select>
-
sql,include
抽取可重用的 sql 字符串段
include 内还可以定义 property 标签,可在 sql 标签表中使用
${}
取值,注意不可以使用#{}
取值来取出<sql id="setectUser"> select * from user </sql> <select id="findUserById"> <include refid="setectUser"></include> where id = #{id} </select>
内置参数
_parameter
,代表整个参数,如果传递过来的是单个简单参数,那么_parameter
就是这个参数,多个参数时候,参数会被封装成一个 map,_parameter
就代表这个 map
_databaseId
,如果配置了 databaseIdProvider 标签,那么_databaseId
就是代表当前数据库的别名
<databaseIdProvider type="VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<select id="findUserById" parameterType="int" resultType="entity.User" databaseId="mysql">
...
</select>
<select id="findUserById" parameterType="int" resultType="entity.User" databaseId="mysql">
...
</select>
可以变成这样
<select id="findUserById" parameterType="int" resultType="entity.User">
<if test="_databaseId=='db2'">
....
</test>
<if test="_databaseId=='oracle'">
</test>
</select>
缓存
缓存可以极大的提升查询效率
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制
MyBatis 系统中默认定义了两级缓存:一级缓存和二级缓存。
-
默认情况下,只有一级缓存( SqlSession 级别的缓存,也称为本地缓存)开启
-
二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存
-
为了提高扩展性。 MyBatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存
一级缓存
一级缓存(local cache), 即本地缓存, 作用域默认为 sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空
同样的查询不会发送两次 sql 语句,返回的也是同一个对象
本地缓存不能被关闭 , 但可以调用 clearCache()来清空本地缓存, 或者改变缓存的作用域
在 mybatis3.1 之后, 可以配置本地缓存的作用域,在主配置文件中配置
失效条件
SqlSession 不同,对于不同的 SqlSession,执行同一条 sql 语句会有两次请求
SqlSession 相同,执行不同的查询条件
SqlSession 相同,两次相同 sql 查询之间有增删改操作
SqlSession 相同,手动清除以及缓存,sqlsession.clearCache()
二级缓存
全局的缓存,基于 namespace 级别的缓存,一个 namespace 对应一个二级缓存
工作机制:
-
一个会话,查询一条语句,这个数据就会被放在当前会话的以及缓存中
-
如果会话关闭,一级缓存的数据将会保存到二级缓存中,新的回话查询信息就可以参考二级缓存
-
不同的 namespace 查出来的数据会放在自己对应的缓存中(map)
使用步骤
-
配置开启
<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在 mapper 中添加一个 cache 标签
一般使用
<cache></cache>
即可<cache eviction="缓存回收策略" blocking="" flushInterval="缓存刷新时间间隔" readOnly="是否只读" size="缓存多少个元素" type="指定自定义缓存的全类名,实现Cache接口"> </cache>
-
POJO 实现序列化 Serializable 接口
细节
-
select 标签里的 useCache 仅对二级缓存生效,以及缓存是一致都使用的
-
增删改标签的 flushCache 默认为 false
-
增删改标签的 flushCache 默认为 true,一级缓存和二级缓存都会会被清空
-
sqlsession.clearCache()只是清空一级缓存
-
3.3 版本之后的 localCacheScope
与第三方 cache 插件整合
去https://github.com/mybatis查看官方的缓存整合项目和下载 jar 文件
比如使用 ehcache,需要整合包 mybatis-ehcache
jar 包准备好之后,创建 ehcache.xml 文件
<mapper namespace="">
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="" value=""/>
<!-- ... -->
</cache>
<!-- ... -->
</mapper>
逆向工程
运行命令 mybatis-generator:generate
maven 的方式
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
<classPathEntry location="E:\apache-maven-3.3.9\local-repo\mysql\mysql-connector-java\5.1.39\mysql-connector-java-5.1.39.jar" />
<context id="mysql_cms" targetRuntime="MyBatis3">
<!-- 可选的,注释生成器 -->
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- 必须的,数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection
driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/cms?characterEncoding=utf-8"
userId="tmaize"
password="123456">
</jdbcConnection>
<!-- 可选的(0 or 1) -->
<!-- 类型转换器或者加类型解析器 -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer true,把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- 必须的,java模型生成器 -->
<javaModelGenerator targetPackage="net.tmaize.cms.entity" targetProject="src/main/java" />
<!-- 必须的,map xml 生成器 -->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources" />
<!-- 可选的,mapper或者就是dao接口生成器 -->
<javaClientGenerator targetPackage="net.tmaize.cms.dao" targetProject="src/main/java" type="XMLMAPPER" />
<!-- 必须的(1...N) -->
<!-- pojo 实体生成器 -->
<!-- tableName:用于自动生成代码的数据库表;domainObjectName:对应于数据库表的javaBean类名 -->
<!-- schema即为数据库名 可不写 -->
<table tableName="user" domainObjectName="User" enableInsert="true">
<!-- 忽略字段 可选的(0 or 1) -->
<!-- <ignoreColumn column="is_use" /> -->
</table>
</context>
</generatorConfiguration>