学习地址:点击打开链接
一、概要
什么是MyBatis:Mybatis是ibatis的新版本,它是一种支持SQL、存储过程和高级映射的持久层框架。它几乎避免了所有数据库的JDBC连接管理代码、手工设置参数、获取结果集等代码、事务管理代码。它可以将XML配置的对象映射到数据库记录。
优点:简单易学、灵活(sql写在xml里,便于统一管理)、解除sql与程序代码的耦合、提供对象映射标签、提供对象关系映射标签、提供编写动态sql的xml标签。
缺点:编写sql语句的工作量大、移植性差(sql语句依赖于数据库)、二级缓存机制不佳(会有脏数据)。
mybatis项目的目录结构:
/my_application
/bin
/devlib
/lib <-- MyBatis *.jar文件在这里。
/src
/org/myapp/
/action
/data <-- MyBatis配置文件在这里, 包括映射器类, XML配置, XML映射文件。
/mybatis-config.xml
/BlogMapper.java
/BlogMapper.xml
/model
/service
/view
/properties <-- 在你XML中配置的属性 文件在这里。
/target
/org/myapp/
/action
/data
/model
/service
/view
/properties
/web
/WEB-INF
/web.xml
二、XML配置
*属性配置:读取顺序为:属性文件定义的属性->resource/url 属性指定的属性->参数传递的属性
*设置配置:用于设置mybatis的行为,如懒加载、缓存等。
*类全限定名的别名配置:用于简化类的全限定名冗余。
*类型处理器配置:用于处理sql执行后结果到对象的转换。有注解配置方法和
*对象工厂配置:用于创建sql处理结果对应的对象。
*插件配置:用于拦截Executors、ParameterHandler、ResultSetHandler和StatementHandler等的部分方法调用。具体地,实现Interceptor接口,指定想要拦截的方法签名,在intercept()方法中可以实现方法调用监控等功能,类似于AOP。
*环境配置:配置数据库连接管理的相关信息,包括事务管理器(JDBC、MANAGED)、数据源(选择POOLED、UNPOOLED、JNDI三种类型中的一种,定义数据库连接的信息)、databaseIdProvier(用于处理不同数据库)。
*映射器配置:支持映射文件的类相对路径配置、映射文件的绝对路径、接口的全限定名和注册包下的所有接口四种类型。
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<package name="org.mybatis.builder"/>
</mappers>
三、XML映射
(1)<insert>|<update>|<delete>
可定义的属性有:id、parameterType、keyProperty、keyColumn、useCache、outTime、statementType、databaseId。
可用的子元素:<selectKey/>
(2)<select>
可定义的属性有:(1)中包含的、resultType、resultMap、fetchSize等。
(3)<sql>
sql标签用于定义可被重用的sql语句:
<sql id="mysql">${table.name},${table.age},${table.gener},${table.favorition}</sql>
<select id="selectUser">
select
<include refid="mysql"><property name="table" value="user"/></include>
from user
</select>
(4)参数
使用#{}会使MyBatis创建预处理语句参数,并用?来标识该参数。
简单类型参数:int、short、float、boolean等:
<select id="selectUser">
select * from user where id=#{id}
</select>
对象类型参数:
<insert id="insertUser" parameterType="User">
insert into user(name,age,gender) values(#{name},#{age,jdbcType=int,javaType=int},#{gender,typeHandler=MyHandler})
</select>
<insert id="insertUser" parameterType="User">
insert into tree(height,age) values(#{height,javaType=double,jdbcType=numeric,numericScale=2},#{age})
</select>
注意:需要为空类型指定jdbcType。
输出参数:用mode=OUT属性
<select id="selectDepartment">
select #{department,mode=OUT,jdbcType=CURSOR, javaTypeResultSet, resultMap=departmentResultMap} from department where id=#{id}
</select>
结构体输类型参数:
<select id="selectDepartment">
select #{department,mode=OUT,jdbcType=STRUCT, javaTypeResultSet, resultMap=departmentResultMap} from department where id=#{id}
</select>
不变字符串参数:存在sql注入的危险。
order by ${columnName}
(5)resultMap
基于Map的简单映射:
<select id="selectUsers" resultType="Map">
select id,username,hashedPassword
from manager
where id=#{id}
</select>
基于JavaBean的简单映射:这种情况下,MyBatis会自动在后台建立一个resultMap
<!-- mybatis-config.xml -->
<typeAlias type="com.luoqing.model.Manager" alias="Manager">
<!-- SQL Mapping XML file-->
<select id="selectUsers" resultType="Manager">
select
user_id as "id", <!-- 解决列名不匹配的一种方案 -->
user_username as "username",
hashed_password as "hashedPassword"
from manager
where id=#{id}
</select>
resultMap用于基于JavaBean的映射:
<resultMap id="managerResultMap" type="Manager">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="pswd" column="password/>
</resultMap>
<select id="selectUsers" resultMap="managerResultMap">
select
user_id,username,pswd
from manager
where user_id=#{id}
</select>
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.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>
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>//用于支持私有属性的构造方法注入,这样就不必暴露该属性的公有接口
<idArg column="blog_id" javaType="int"/>
<arg column... javaType.../>
</constructor>
<result column="blog_title" property="title" />
<association property="author" column="blog_author_id" javaType="Author">//作者对象属性
<id column="blog_title" property="id">
<result column="author_username" property="username"/>
<result column="author_password" property="password"/>
<result column="author_email" property="email"/>
<result column="author_bio" property="bio"/>
</association>
//或者
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
<collection property="posts" ofType="Post">//博文集合属性
<id column="post_id" property="id"/>
<result column="post_subject" property="subject"/>
<collections property="comments" javaType="Comment">
<id column="comment_id" property="id"/>
</collections>
<collection property="tags" ofType="Tag">
<id column="draft_id" property="id"/>
</collection>
</collection>
<discriminator column="draft" javaType="int">//依据查询结果设置对象属性值
<case value="1" resultType="DraftPost"/>
</discriminator>
</resultMap>
关联association:前面的resultMap通过嵌套结果来进行对象的关联,其实还有两种更灵活的处理方式,即嵌套查询(如下例)和嵌套结果映射(如下下例):嵌套查询可能会导致N+1问题,懒加载在不需要立即使用查询的属性时可以解决该问题,将查询分散在不同时段,但如果要立即使用刚刚查询的属性,懒加载只会让性能变得更糟。
<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 id="blogResult" type="Blog">
<association column="author_id" property="author" select="selectAuthor"/>
</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
CA.id as co_author_id,
CA.username as co_author_username,
CA.password as co_author_password,
CA.email as co_author_email,
CA.bio as co_author_bio
from Blog B left outer join Author A on B.author_id = A.id
left outer join Author CA on B.co_author_id = CA.id
where B.id = #{id}</select><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"/>
<association property="coAuthor" column="co_author_id" javaType="Author" resultMap="authorResult" columnPrefix="co_"/>//复用authorResult
</resultMap>
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="pswd" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="bio"/>
</resultMap>
集合collection:和关联类似,除了结果嵌套外,还有查询嵌套和结果映射嵌套两种类型:
<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="Blog">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>
<resultMap id="blogPostResult" type="Post">
<id property="id" column="id"/>
<result property="subject" column="subject"/>
<result property="body" column="body"/>
</resultMap>
鉴别器:
<resultMap>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="suvResult"/>
</discriminator>
</resultMap>
自动映射:
*通常数据库采用_ID_类型的大写、下划线命名,而java采用驼峰命名,为了让Mybatis实现自动映射,需要将mapUnderscoreToCamelCase设为true。
*MyBatis自动映射不区分大小写。
*MyBatis都是先自动映射,自动映射完了才进行手工映射。当resultMap中没有为属性配置手工映射,MyBatis会自动映射。
*自动映射包含NONE、PARTIAL、FULL三种级别,默认值是PARTIAL。
缓存:
在映射文件开头加上下列配置即开启了二级缓存。
<cache/>
默认情况下,缓存具有如下特性:
- 映射语句文件中的所有 select 语句将会被缓存。
- 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
- 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
- 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
也可以修改上述配置:
<cache
eviction="FIFO" //LRU、FIFO、SOFT、WEAK
flushInterval="60000"
size="512"
readOnly="true"/>
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
四、动态SQL
动态SQL一般作用于WHERE子句中,用于条件查询。
动态SQL包括四种语句:<if>、<choose><when><otherwise>、<trim><where><set>、<foreach>。
使用示例:
//if
<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>
//choose when otherwise
<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,where元素会依据if条件成立的个数动态添加where关键字,也会在当where子句中以AND、OR开头时去掉连接词
<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>
//set,用于条件更新,set元素会依据if条件成立的个数动态决定是否添加set关键词,也会去掉set从句中多余的逗号
<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>
//trim,将prefixOverrides中给出的关键词替换为prefix中指定的关键词
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<trim prefix="SET" suffixOverrides=",">
...
</trim>
//foreach,当把List或Array对象传给MyBatis时,MyBatis会将该对象封装在一个Map中,并以类的小写名称为键,以该对象为值。即List会以"list"为键,而ArrayList会以array为键。
<select id="selectPostIn" resultType="Post">
SELECT * FROM post WHERE ID IN
<foreach item="item" index="index" collection="list"
open="(" seperator="," close=")">
#{item}
</foreach>
</select>
//bind,该元素可以使用OGNL表达式语言从上下文中获取一个变量
<select id="selectBologsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'">
select * from blog where title like #{pattern}
</select>
//多数据库支持,通过_databaseId来判断当前的databaseIdProvider
<insert id="insert">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
<if test="_databaseId=='oracle'">
select seq_user.nextval from dual
</if>
<if test="_databaseId='db2'">
select nextval for seq_user from sysibm.sysdummy1
</if>
</selectKey>
insert into user values(#{id},#{name})
</insert>
五、Java API
SqlSessionFactoryBuilder->SqlSessionFactory->SqlSession->Mapper/Transaction
如果使用Spring+Mybatis,那么创建SqlSession的工作将交给Spring来做,而不需要我们手工创建。
1.SqlSessionFactoryBuilder
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配置创建SqlSessionFactory
String resource="org/mybatis/builder/mybatis-config.xml";
InputStream inputStream=Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory=builder.build(inputStream);
使用示例:使用Configuration来创建SqlSessionFactory。Configuration包含所有mybatis-config.xml配置的配置方法。
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);
2.SqlSessionFactory
SqlSession openSession()//开启事务(不自动提交)、从配置读取数据源、使用数据源默认的事务隔离级别、预处理语句不会被复用,也不会批量处理更新
SqlSession openSession(boolean autoCommit)//是否自动提交
SqlSession openSession(Connection connection)//给定数据源
SqlSession openSession(TransactionIsolationLevel level)//设置自动隔离级别
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)//ExecutorType.SIMPLE为每个语句执行创建新的预处理语句、ExecutorType.REUSE重用预处理语句、ExecutorType.BATCH批量执行
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();
3.SqlSession
1)执行语句方法
T selectOne(String statement, Object parameter)//必须返回一个,返回多个或0个都会抛出异常
List selectList(String statement, Object parameter)
Map selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
//无参重载版本
T selectOne(String statement)
List selectList(String statement)
Map selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)
//高级版本
<E> List<E> selectList (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)
2)事务控制方法
//只有在设置transactionManager为JDBC才有效,设置为MANAGED时,SqlSession没有权限管理事务了
//默认情况下,MyBatis在执行了插入、删除、修改操作会执行提交事务,我们不需要手动提交。如果你修改了数据库,但没有执行增删改操作,你可以传入true参数,强制MyBatis提交。
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
3)清理缓存
//SqlSession实例在执行update、commit、rollback和close时会默认清除本地缓存。如果需要显示清除,看调用下面的方法
void clearCache()
4)关闭SqlSession
//最好是将close()方法放到finally块中,以确保session关闭
void close()
5)获取配置
Configuration getConfiguration()
6)使用映射器
T getMapper(Class type)
下面展示了一个接口是如何映射到SqlSession对应的方法的:
//用SqlSession获取Mapper时,只需要将下面这个接口的class对象传递给getMapper方法
//这个接口不需要进行任何实现,接口方法只需要对应映射语句即可
public interface AuthorMapper{
//Author selectOne("selectAuthor",5);
Author selectAuthor(int id);
//List selectList("selectAuthors");
List selectAuthors();
//Map selectMap("selectAuthors","id");
@MapKey("id")
Map selectAuthors();
//void insert("insertAuthor",author);
int insertAuthor(Author author);
//void update("updateAuthor",author);
int updateAuthor(Author author);
//void delete("deleteAuthor",author);
int deleteAuthor(Author author);
}
六、SQL构建器
使用SQL类来构建SQL映射示例:个人感觉就是每个Sql关键词对应一个方法。
private String selectPersonSql() {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}}.toString();
}
public String deletePersonSql() {
return new SQL() {{
DELETE_FROM("PERSON");
WHERE("ID = ${id}");
}}.toString();
}
public String selectPersonLike(final String id, final String firstName, final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID like ${id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME like ${firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME like ${lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
SQL类中的方法:
1.SELECT(String)、SELECT_DISTINCT(String)、FROM(String)、JOIN(String)、INNER_JOIN(String)、LEFT_OUTER_JOIN(String)、RIGHT_OUTER_JOIN(String)、WHERE(String)、OR()、AND()、GROUP_BY(String)、HAVING(String)、ORDER_BY(String)、
2.DELETE_FROM(String)、INSERT_INTO(String)、SET(String)、UPDATE(String)、VALUES(String, String)