mybatis是一个非常好的持久层框架,有字段映射,有sql缓存,还能很好的和spring做集成,用的人真是非常非常多,尤其是互联网公司,基本上不用hibernate的都在用mybatis。
mybatis的优点主要体现在以下几个方面:
- java实体和数据库字段做映射,简化jdbc操作,不用写各种各样的set、get字段操作;
- 可以利用第三方的工具,根据model对象,自动生成xml、mapper配置文件,基本不用写sql;
- sql集中存储,上线后如果sql有问题,可以直接改配置,不用编译java,重启就能运行;
- mybatis的缓存,这个意义也不是很大,如果真到了靠这点缓存来提高性能的地步了,估计就不单单是mybatis的原因了。
缺点也有不少,简单列几点:
- mybatis里面的特殊字符,比如<>等,很容易造成错误,且mybatis的错误提示不是很具体;
- mybatis更新比较缓慢,其实这几年都没什么更新,而且也没什么要更新的了。确实是这样,mybatis后来新加的基于注解的操作,替代xml,意义不是很大,基本很少人用,mybatis的核心在于mapper映射文件,改为注解后,映射文件不存在了;
- 调试比较麻烦,主要是因为如果改mapper配置文件,工程需要重启,不知道现在的jrebel能不能动态加载mapper文件。
1. mybatis支持的映射方式
mybatis支持的映射方式有基于xml的mapper.xml文件、基于java的使用Mapper接口class,简单学习一下mybatis使用接口来配置映射的方法。
接口方法注解主要是四个:@Insert、@Delete、@Update、@Select
2. 如何使用接口注解来映射
下面的实验都是基于t_user表的,其结构如下:
DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY , username VARCHAR(100) NOT NULL , passwd CHAR(32) NOT NULL, birth_day DATETIME ) CHARSET UTF8;
2.1 增 @Insert
插入记录的时候麻烦的一点是主键如何生成,对此基本上有三种方案,分别是手动指定(应用层)、自增主键(数据层单表)、选择主键(数据层多表)。
1. 在应用层手动指定主键
手动指定的方式不把主键区别看待,插入之前在应用层生成对象的时候就会给主键一个值,插入的时候与普通字段没啥区别。
/** * 插入记录,手动分配主键 * * @param user * @return */ @Insert("INSERT INTO t_user (id, username, passwd) VALUES (#{id}, #{username}, #{passwd})") int addUserAssignKey(User user);
在上面的这个例子中,mybatis并不知道到底哪个字段是主键,id虽然是主键字段,但并没有被区别对待。
注意#{username}这种写法,是把User作为了当前上下文,这样访问User的属性的时候直接写属性名字就可以了。
2. 表自增主键
自增主键对应着XML配置中的主键回填,一个简单的例子:
/** * 插入记录,数据库生成主键 * * @param user * @return */ @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("INSERT INTO t_user (username, passwd) VALUES (#{username}, #{passwd})") int addUserGeneratedKey(User user);
使用Option来对应着XML设置的select标签的属性,userGeneratordKeys表示要使用自增主键,keyProperty用来指定主键字段的字段名。
自增主键会使用数据库底层的自增特性。
3. 选择主键
选择主键从数据层生成一个值,并用这个值作为主键的值。
/** * 插入记录,选择主键 * * @param user * @return */ @Insert("INSERT INTO t_user (username, passwd) VALUES (#{username}, #{passwd})") @SelectKey(statement = "SELECT UNIX_TIMESTAMP(NOW())", keyColumn = "id", keyProperty = "id", resultType = Long.class, before = true) int addUserSelectKey(User user);
2.2 删 @Delete
删除的时候只要把语句条件神马的写在@Delete注解的value里就好了,返回一个int类型是被成功删除的记录数。
/** * 删除记录 * * @param id * @return */ @Delete("DELETE FROM t_user WHERE id=#{id}") int delete(Long id);
2.3 改 @Update
修改的时候和删除一样只要把SQL语句写在@Update的value中就好了,返回一个int类型表示被修改的记录行数。
/** * 修改记录 * * @param user * @return */ @Update("UPDATE t_user SET username=#{username}, passwd=#{passwd} WHERE id=#{id}") int update(User user);
2.4 查 @Select
查询的时候稍稍有些复杂,因为查询会涉及到如何将查出来的字段设置到对象上,通常有那么三种办法:
1. 在SQL语句中手动指定别名来匹配
在写SQL语句的时候,手动为每一个字段指定一个别名来跟对象的属性做匹配,适用于表字段名与对象属性名差异很大没有规律并且表字段不多的情况。
/** * 根据ID查询,手动设置别名 * * @param id * @return */ @Select("SELECT id, username, passwd, birth_day AS birthDay FROM t_user WHERE id=#{id}") User loadByIdHandAlias(Long id)
2. 使用mybatis的自动下划线驼峰转换
mybatis有一个选项叫mapUnderscoreToCamelCase,当表中的字段名与对象的属性名相同只是下划线和驼峰写法的差异时适用。
配置了mapUnderscoreToCamelCase之后mybatis在将ResultSet查出的数据设置到对象的时候会尝试先将下划线转换为驼峰然后前面拼接set去设置属性。
开启转换:
然后查询:
/** * 根据ID查询,开了自动驼峰转换 * * @param id * @return */ @Select("SELECT * FROM t_user WHERE id=#{id}") User loadByIdAutoAlias(Long id);
查看打印的结果,birth_day属性填充到了对象中:
3. 使用ResultMap
对于表的字段名和对象的属性名没有太大相同点并且表中的字段挺多的情况下,应该使用ResultMap做适配。
/** * 使用ResultMap * * @param id * @return */ @Results(id = "userMap", value = { @Result(id=true, column = "id", property = "id"), @Result(column = "username", property = "username"), @Result(column = "passwd", property = "passwd"), @Result(column = "birth_day", property = "birthDay") }) @Select("SELECT * FROM t_user WHERE id=#{id}") User loadByIdResultMap(Long id);
@Results对应着XML中的ResultMap,同时可以为其指定一个id,其它地方可以使用这个id来引用它,比如要引用上面的这个Results:
/** * 引用其他的Result * * @param id * @return */ @ResultMap("userMap") @Select("SELECT * FROM t_user WHERE id=#{id}") User loadByIdResultMapReference(Long id);
使用@ResultMap来引用一个已经存在的ResultMap,这个ResultMap可以是在Java中使用@Results注解定义的,也可以是在XML中使用resultMap标签定义的。
2.5 样例代码
User.java
package org.cc11001100.mybatis.domain; import org.apache.ibatis.type.Alias; import java.util.Date; /** * @author: CC11001100 * @date: 2017/11/9 18:33 * @email: CC11001100@qq.com */ @Alias("user") public class User { private Long id; private String username; private String passwd; private Date birthDay; public User() { } public User(String username, String passwd) { this.username = username; this.passwd = passwd; } public User(Long id, String username, String passwd) { this.id = id; this.username = username; this.passwd = passwd; } public Date getBirthDay() { return birthDay; } public void setBirthDay(Date birthDay) { this.birthDay = birthDay; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", passwd='" + passwd + '\'' + ", birthDay=" + birthDay + '}'; } }
UserMapper.java
package org.cc11001100.mybatis.mapper; import org.apache.ibatis.annotations.*; import org.cc11001100.mybatis.domain.User; import org.springframework.stereotype.Repository; /** * @author: CC11001100 * @date: 2017/11/9 18:16 * @email: CC11001100@qq.com */ @Mapper @Repository public interface UserMapper { /** * 插入记录,手动分配主键 * * @param user * @return */ @Insert("INSERT INTO t_user (id, username, passwd) VALUES (#{id}, #{username}, #{passwd})") int addUserAssignKey(User user); /** * 插入记录,数据库生成主键 * * @param user * @return */ @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("INSERT INTO t_user (username, passwd) VALUES (#{username}, #{passwd})") int addUserGeneratedKey(User user); /** * 插入记录,选择主键 * * @param user * @return */ @Insert("INSERT INTO t_user (username, passwd) VALUES (#{username}, #{passwd})") @SelectKey(statement = "SELECT UNIX_TIMESTAMP(NOW())", keyColumn = "id", keyProperty = "id", resultType = Long.class, before = true) int addUserSelectKey(User user); /** * 删除记录 * * @param id * @return */ @Delete("DELETE FROM t_user WHERE id=#{id}") int delete(Long id); /** * 修改记录 * * @param user * @return */ @Update("UPDATE t_user SET username=#{username}, passwd=#{passwd} WHERE id=#{id}") int update(User user); /** * 根据ID查询,手动设置别名 * * @param id * @return */ @Select("SELECT id, username, passwd, birth_day AS birthDay FROM t_user WHERE id=#{id}") User loadByIdHandAlias(Long id); /** * 根据ID查询,开了自动驼峰转换 * * @param id * @return */ @Select("SELECT * FROM t_user WHERE id=#{id}") User loadByIdAutoAlias(Long id); /** * 使用ResultMap * * @param id * @return */ @Results(id = "userMap", value = { @Result(id=true, column = "id", property = "id"), @Result(column = "username", property = "username"), @Result(column = "passwd", property = "passwd"), @Result(column = "birth_day", property = "birthDay") }) @Select("SELECT * FROM t_user WHERE id=#{id}") User loadByIdResultMap(Long id); /** * 引用其他的Result * * @param id * @return */ @ResultMap("userMap") @Select("SELECT * FROM t_user WHERE id=#{id}") User loadByIdResultMapReference(Long id); }
注解有下面这些:
注解 | 目标 | 相对应的 XML | 描述 |
---|---|---|---|
@CacheNamespace | 类 | <cache> | 为给定的命名空间 (比如类) 配置缓存。 属性:implemetation,eviction, flushInterval,size 和 readWrite。 |
@CacheNamespaceRef | 类 | <cacheRef> | 参照另外一个命名空间的缓存来使用。 属性:value,应该是一个名空间的字 符串值(也就是类的完全限定名) 。 |
@ConstructorArgs | Method | <constructor> | 收集一组结果传递给一个劫夺对象的 构造方法。属性:value,是形式参数 的数组。 |
@Arg | 方法 |
|
单 独 的 构 造 方 法 参 数 , 是 ConstructorArgs 集合的一部分。属性: id,column,javaType,typeHandler。 id 属性是布尔值, 来标识用于比较的属 性,和<idArg>XML 元素相似。 |
@TypeDiscriminator | 方法 | <discriminator> | 一组实例值被用来决定结果映射的表 现。 属性: column, javaType, jdbcType, typeHandler,cases。cases 属性就是实 例的数组。 |
@Case | 方法 | <case> | 单独实例的值和它对应的映射。属性: value,type,results。Results 属性是结 果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。 |
@Results | 方法 | <resultMap> | 结果映射的列表, 包含了一个特别结果 列如何被映射到属性或字段的详情。 属 性:value, id。value 属性是 Result 注解的数组。 The id attribute is the name of the result mapping. |
@Result | 方法 |
|
在列和属性或字段之间的单独结果映 射。属 性:id,column, property, javaType ,jdbcType ,type Handler, one,many。id 属性是一个布尔值,表 示了应该被用于比较(和在 XML 映射 中的<id>相似)的属性。one 属性是单 独 的 联 系, 和 <association> 相 似 , 而 many 属 性 是 对 集 合 而 言 的 , 和 <collection>相似。 它们这样命名是为了 避免名称冲突。 |
@One | 方法 | <association> | 复杂类型的单独属性值映射。属性: select,已映射语句(也就是映射器方 法)的完全限定名,它可以加载合适类 型的实例。注意:联合映射在注解 API 中是不支持的。这是因为 Java 注解的 限制,不允许循环引用。 fetchType, which supersedes the global configuration parameterlazyLoadingEnabled for this mapping. |
@Many | 方法 | <collection> | A mapping to a collection property of a complex type. Attributes: select, which is the fully qualified name of a mapped statement (i.e. mapper method) that can load a collection of instances of the appropriate types,fetchType, which supersedes the global configuration parameterlazyLoadingEnabled for this mapping. NOTE You will notice that join mapping is not supported via the Annotations API. This is due to the limitation in Java Annotations that does not allow for circular references. |
@MapKey | 方法 | 复 杂 类 型 的 集合 属 性 映射 。 属 性 : select,是映射语句(也就是映射器方 法)的完全限定名,它可以加载合适类 型的一组实例。注意:联合映射在 Java 注解中是不支持的。这是因为 Java 注 解的限制,不允许循环引用。 | |
@Options | 方法 | 映射语句的属性 | 这个注解提供访问交换和配置选项的 宽广范围, 它们通常在映射语句上作为 属性出现。 而不是将每条语句注解变复 杂,Options 注解提供连贯清晰的方式 来访问它们。属性:useCache=true , flushCache=FlushCachePolicy.DEFAULT , resultSetType=FORWARD_ONLY , statementType=PREPARED , fetchSize=-1 , , timeout=-1 useGeneratedKeys=false , keyProperty=”id” , keyColumn=”” , resultSets=””。 理解 Java 注解是很 重要的,因为没有办法来指定“null” 作为值。因此,一旦你使用了 Options 注解,语句就受所有默认值的支配。要 注意什么样的默认值来避免不期望的 行为。 |
|
方法 |
|
这些注解中的每一个代表了执行的真 实 SQL。 它们每一个都使用字符串数组 (或单独的字符串)。如果传递的是字 符串数组, 它们由每个分隔它们的单独 空间串联起来。这就当用 Java 代码构 建 SQL 时避免了“丢失空间”的问题。 然而,如果你喜欢,也欢迎你串联单独 的字符串。属性:value,这是字符串 数组用来组成单独的 SQL 语句。 |
|
方法 |
|
这些可选的 SQL 注解允许你指定一个 类名和一个方法在执行时来返回运行 允许创建动态 的 SQL。 基于执行的映射语句, MyBatis 会实例化这个类,然后执行由 provider 指定的方法. 该方法可以有选择地接受参数对象.(In MyBatis 3.4 or later, it’s allow multiple parameters) 属性: type,method。type 属性是类。method 属性是方法名。 注意: 这节之后是对 类的 讨论,它可以帮助你以干净,容于阅读 的方式来构建动态 SQL。 |
@Param | Parameter | N/A | 如果你的映射器的方法需要多个参数, 这个注解可以被应用于映射器的方法 参数来给每个参数一个名字。否则,多 参数将会以它们的顺序位置来被命名 (不包括任何 RowBounds 参数) 比如。 #{param1} , #{param2} 等 , 这 是 默 认 的 。 使 用 @Param(“person”),参数应该被命名为 #{person}。 |
@SelectKey | Method | <selectKey> | This annotation duplicates the <selectKey> functionality for methods annotated with @Insert, @InsertProvider, @Update or@UpdateProvider. It is ignored for other methods. If you specify a@SelectKey annotation, then MyBatis will ignore any generated key properties set via the @Options annotation, or configuration properties. Attributes: statement an array of strings which is the SQL statement to execute, keyProperty which is the property of the parameter object that will be updated with the new value, before which must be either true orfalse to denote if the SQL statement should be executed before or after the insert, resultType which is the Java type of the keyProperty, andstatementType=PREPARED. |
@ResultMap | Method | N/A | This annotation is used to provide the id of a <resultMap> element in an XML mapper to a @Select or @SelectProvider annotation. This allows annotated selects to reuse resultmaps that are defined in XML. This annotation will override any @Results or @ConstructorArgs annotation if both are specified on an annotated select. |
@ResultType | Method | N/A | This annotation is used when using a result handler. In that case, the return type is void so MyBatis must have a way to determine the type of object to construct for each row. If there is an XML result map, use the @ResultMap annotation. If the result type is specified in XML on the<select> element, then no other annotation is necessary. In other cases, use this annotation. For example, if a @Select annotated method will use a result handler, the return type must be void and this annotation (or @ResultMap) is required. This annotation is ignored unless the method return type is void. |
@Flush | Method | N/A |
If this annotation is used, it can be called theSqlSession#flushStatements() via method defined at a Mapper interface.(MyBatis 3.3 or above) |
3. 总结
使用接口注解的优点:
1. 比较方便,快速编写映射语句
使用接口注解的缺点:
1. 适用于比较简单的配置,当太复杂了接口就搞不定了。
2. 不能使用动态SQL,有点鸡肋。