MyBatis真正的强大,在于其映射语句的魔力。
SQL 映射文件有很少的几个*元素(按照它们应该被定义的顺序):
(1)cache 给定命名空间的配置缓存。(2)cache-ref 其他命名空间缓存配置的引用。
(3)resultMap 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
(4)sql 可被其他语句引用的可重用语句块。
(5)insert 映射插入语句
(6)update 映射更新语句
(7)delete 映射删除语句
(8)select 映射查询语句
在我们描述每个元素细节之前,我们先感受一些元素的使用实例,
以下实例分为UserMainMapper.XML配置的XML版本和java注解版本,
也可以根据实际情况两种都用,但是同名方法只能用XML或java注解,
先不多少,看实例,这里将入门体验文章做简化,分析元素。
一,准备条件
--jar
mybatis-3.2.2.jar --mybatisjar
ojdbc6.jar --数据库驱动,这个地方用的是oracle,而入门体验中用的是Mysql,所以驱动不一样
junit-4.4.jar --单元测试
--数据库表
--建表 CREATE TABLE t_user_main( f_id NUMBER(3) PRIMARY KEY, f_username VARCHAR(4), f_age NUMBER(3) ); --设置主键自增长 CREATE SEQUENCE user_sequence INCREMENT BY 1 -- 每次加几个 START WITH 1 -- 从1开始计数 NOMAXVALUE -- 不设置最大值 NOCYCLE -- 一直累加,不循环 --插入测试数据 INSERT INTO t_user_main(f_id,f_username,f_age)VALUES(1,'one',25); INSERT INTO t_user_main(f_id,f_username,f_age)VALUES(2,'two',25); COMMIT; --查询 SELECT * FROM t_user_main;
二,项目结构
三,test-jdbc.properties和mybatis-config.xml配置
test-jdbc.properties:
jdbc.driver=oracle.jdbc.driver.OracleDriver jdbc.url=jdbc:oracle:thin:@localhost:1521/XE jdbc.username=system jdbc.password=123456
mybatis-config:
<?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> <!-- 属性(properties) --> <properties resource="test-jdbc.properties"/> <!-- 设置(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> --> <!-- 别名(typeAliases) --> <!-- <typeAliases> <typeAlias alias="User" type="com.lanhuigu.mybatis.entity.User"/> </typeAliases> --> <!-- environments环境 *environment 环境变量 *transactionManager 事务管理器 *dataSource 数据源 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!-- mappers映射器 --> <mappers> <!-- <mapper resource="com/lanhuigu/mybatis/map/UserMainMapper.xml"/> --> <package name="com.lanhuigu.mybatis.map"/> </mappers> </configuration>
四,User.java,UserMainMapper.java接口和UserMainMapper.xml配置(Mapper)
User.java
package com.lanhuigu.mybatis.entity; import java.io.Serializable; public class User implements Serializable{ private static final long serialVersionUID = -3412068097348759984L; private Integer id; private String username; private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
UserMainMapper.java(接口)
package com.lanhuigu.mybatis.map; import java.util.List; import java.util.Map; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import com.lanhuigu.mybatis.entity.User; public interface UserMainMapper { //=======================XML版======================= /** * 查询用户对象--返回User对象 */ public User queryUserMainById(int id); /** * 查询用户对象--返回多个--List<User> */ public List<User> queryUserMainList(); /** * 查询用户对象--返回map */ public Map<String,Object> queryUserMainResultMap(int id); /** * 查询用户对象--返回多个--List<Map<String,Object>> */ public List<Map<String,Object>> queryUserMainResultListMap(); /** * 插入用户对象 */ public int insertUser(User user); /** * 更新用户对象 */ public int updateUser(User user); /** * 删除用户对象 */ public int deleteUser(int id); //=======================注解版========================== /** * 查询用户对象--返回User对象 */ @Select(" select f_id id,f_username username,f_age age from t_user_main where f_id = #{id} ") @Options(flushCache=true) public User queryUserMainByIdNew(@Param("id") int id); /** * 查询用户对象--返回多个--List<User> */ @Select("select f_id id,f_username username,f_age age from t_user_main ") @Options(flushCache=true) public List<User> queryUserMainListNew(); /** * 查询用户对象--返回map */ @Select("select f_id id,f_username username,f_age age from t_user_main where f_id = #{id}") @Options(flushCache=true) public Map<String,Object> queryUserMainResultMapNew(@Param("id") int id); /** * 查询用户对象--返回多个--List<Map<String,Object>> */ @Select("select f_id id,f_username username,f_age age from t_user_main") @Options(flushCache=true) public List<Map<String,Object>> queryUserMainResultListMapNew(); /** * 插入用户对象 */ @Insert("insert into t_user_main (f_id,f_username,f_age) " + " values(#{id},#{username},#{age}) ") @Options(useGeneratedKeys=false) public int insertUserNew(User user); /** * 更新用户对象 */ @Update("update t_user_main set f_username=#{username},f_age=#{age} " + " where f_id=#{id} ") public int updateUserNew(User user); /** * 删除用户对象 */ @Delete("delete from t_user_main where f_id=#{id}") @Options(flushCache=true) public int deleteUserNew(@Param("id") int id); }
UserMainMapper.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"> <mapper namespace="com.lanhuigu.mybatis.map.UserMainMapper"> <!-- 查询用户对象,返回User对象 --> <select id="queryUserMainById" parameterType="int" flushCache="true" resultType="com.lanhuigu.mybatis.entity.User"> select f_id id, f_username username, f_age age from t_user_main where f_id = #{id} </select> <!-- 查询用户对象,返回list<User> --> <select id="queryUserMainList" flushCache="true" resultType="com.lanhuigu.mybatis.entity.User"> select f_id id, f_username username, f_age age from t_user_main </select> <!-- 查询用户对象,返回Map --> <select id="queryUserMainResultMap" parameterType="int" flushCache="true" resultType="java.util.HashMap"> select f_id id, f_username username, f_age age from t_user_main where f_id = #{id} </select> <!-- 查询用户对象,返回List<Map<String,Object>> --> <select id="queryUserMainResultListMap" flushCache="true" resultType="java.util.HashMap"> select f_id id, f_username username, f_age age from t_user_main </select> <!-- 插入用户对象(insert) --> <insert id="insertUser" parameterType="com.lanhuigu.mybatis.entity.User" useGeneratedKeys="false"> insert into t_user_main (f_id,f_username,f_age) values(#{id},#{username},#{age}) </insert> <!-- 更新用户对象(update) --> <update id="updateUser" parameterType="com.lanhuigu.mybatis.entity.User" flushCache="true"> update t_user_main set f_username=#{username},f_age=#{age} where f_id=#{id} </update> <!-- 删除用户对象(delete) --> <delete id="deleteUser" parameterType="int" flushCache="true"> delete from t_user_main where f_id=#{id} </delete> </mapper>
五,测试代码
package com.lanhuigu.mybatis; import java.io.InputStream; import java.util.List; import java.util.Map; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import com.lanhuigu.mybatis.entity.User; import com.lanhuigu.mybatis.map.UserMainMapper; public class MyBatisTest { @Test public void testMyBatis() { String resource = "mybatis-config.xml"; SqlSessionFactory sqlSessionFactory = null; SqlSession session = null; try { //读取资源文件 InputStream is = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); session = sqlSessionFactory.openSession(); //mapper调用 UserMainMapper userMainMapper = session.getMapper(UserMainMapper.class); //================XML测试================= //查询--select--返回User对象 User user = userMainMapper.queryUserMainById(1); System.out.println("user="+user); System.out.println(user.getUsername()); //查询--select--返回多个User对象--List<User> /*List<User> list = userMainMapper.queryUserMainList(); for(int i=0;i<list.size();i++ ){ User user = list.get(i); System.out.println(user.getUsername()); }*/ //查询--select--返回map /*Map<String,Object> userMap = userMainMapper.queryUserMainResultMap(1); System.out.println("userMap="+userMap); System.out.println(userMap.get("USERNAME"));*/ //查询--select--返回List<Map<String,Object>> /*List<Map<String,Object>> list = userMainMapper.queryUserMainResultListMap(); System.out.println("ListMap="+list); for (int i=0;i<list.size();i++) { Map<String,Object> userMap = list.get(i); System.out.println(userMap.get("USERNAME")); }*/ //插入--insert /*User user = new User(); user.setId(3); user.setUsername("three"); user.setAge(25); int result = userMainMapper.insertUser(user); if (result > 0) { System.out.println("添加用户成功"); //事务提交 session.commit(); } else { System.out.println("添加用户失败"); //事务回滚 session.rollback(); }*/ //更新--update /*User user = new User(); user.setId(3); user.setUsername("this three update"); user.setAge(25); int result = userMainMapper.updateUser(user); if (result > 0) { System.out.println("更新成功"); //事务提交 session.commit(); } else { System.out.println("更新失败"); //事务回滚 session.rollback(); }*/ //删除--delete /*int result = userMainMapper.deleteUser(3); if (result > 0) { System.out.println("删除成功"); //事务提交 session.commit(); } else { System.out.println("删除失败"); //事务回滚 session.rollback(); }*/ //=================注解测试=================== /* * 只需将xml测试代码copy一份,然后将方法名都加上一个New即可 * 在UserMainMapper.java映射器中原本可以将xml部分注释, * 注解部分方法用同名,但是这样做我们就得将UserMainMapper.xml删掉, * 或者改名,否则注解不能查询出数值,着跟命名空间和class,xml加载有关系. * 总之class中和xml中不能出现注解和xml同时使用,对于单个方法,二者 * 只能存在其一,否则mybatis不知道你到底想干啥,只能什么都不做了 */ //eg. /*User user = userMainMapper.queryUserMainByIdNew(1); System.out.println("user="+user); System.out.println(user.getUsername());*/ } catch (Exception e) { if (session != null) { session.close(); } } } }
有了上面的体验,我们在对每个元素等等进行分析,然后在参考实例,能体验更深。
结合上面的实例对元素进行分析和补充。
select元素
查询语句是MyBatis中最常用的元素,作为一个持久层框架,把数据放入数据库是一个方面,
把数据取一条或多条更是重中之重。简单查询语句:
<select id="queryUserMainById" parameterType="int" flushCache="true" resultType="com.lanhuigu.mybatis.entity.User"> select f_id id, f_username username, f_age age from t_user_main where f_id = #{id} </select>
这个语句称之为queryUserMainById,接受一个int或Integer的参数,
返回一个com.lanhuigu.mybatis.entity.User的对象,这个如果嫌写起来长,
可以在mybatis-config.xml中配置别名,这个地方直接用别名替代。
别名配置:
<typeAliases> <typeAlias alias="User" type="com.lanhuigu.mybatis.entity.User"/> </typeAliases>select语句中参数id符号:
#{id}获取传入的参数。
这样MyBatis处理时底层用JDBC生产预编译语句,JDBC底层MyBatis替我们做了,
这样我们只需关注MyBatis的应用,避免写大量的JDBC代码,提高工作效率,调用时直接执行。
select元素有很多属性可供选择,如下:
<select id="queryUserMainById" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="userMainResultMap" flushCache="false" useCache="true" timeout="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY">select元素相关属性的描述,如下:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
resultType | 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。 |
resultMap | 外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同时使用。 |
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 中的一个,默认值为 unset (依赖驱动)。 |
databaseId | 如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
resultOrdered | 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 |
resultSets | 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。 |
insert,update和delete元素
数据插入(insert),数据更新(update),数据删除(delete)实现大同小异。例如:
<span style="color:#000000;"><!-- 插入用户对象(insert) --> <insert id="insertUser" parameterType="com.lanhuigu.mybatis.entity.User" useGeneratedKeys="false"> insert into t_user_main (f_id,f_username,f_age) values(#{id},#{username},#{age}) </insert> <!-- 更新用户对象(update) --> <update id="updateUser" parameterType="com.lanhuigu.mybatis.entity.User" flushCache="true"> update t_user_main set f_username=#{username},f_age=#{age} where f_id=#{id} </update> <!-- 删除用户对象(delete) --> <delete id="deleteUser" parameterType="int" flushCache="true"> delete from t_user_main where f_id=#{id} </delete></span>这三个元素的属性描述如下:
属性 | 描述 |
---|---|
id | 命名空间中的唯一标识符,可被用来代表这条语句。 |
parameterType | 将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。 |
flushCache | 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值: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 的语句;如果带或者不带的语句都有,则不带的会被忽略。 |
sql元素
发
这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。
它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化。
把上面的实例中UserMainMapper.xml修改,来个简单实例:
<!-- sql语句块 --> <sql id="queryConditionSql"> f_id = #{id} </sql> <!-- 查询用户对象,返回User对象 --> <select id="queryUserMainById" parameterType="int" flushCache="true" resultType="com.lanhuigu.mybatis.entity.User"> select f_id id, f_username username, f_age age from t_user_main <where> <include refid="queryConditionSql"/> </where> </select>将查询语句的条件由where f_id=#{id}修改成sql语句块替代。
这样整个xml中条件部分共同的地方都提出来,避免重复啰嗦。
当然,sql语句块除了可以用于条件部分,还可以用于其它任地方,
比如:
<!-- sql语句块 --> <sql id="queryConditionSql"> f_id = #{id} </sql> <sql id="att"> f_username username, f_age age </sql> <!-- 查询用户对象,返回User对象 --> <select id="queryUserMainById" parameterType="int" flushCache="true" resultType="com.lanhuigu.mybatis.entity.User"> select f_id id, <include refid="att"/> from t_user_main <where> <include refid="queryConditionSql"/> </where> </select>查询时,效果一样。
总结:
(1)sql语句块用于提出xml共同sql
(2)sql语句块可以置于xml文件中查询语句前后,位置无关系,都能调用。
参数(parameters)
但是
MyBatis的参数功能是非常强大的,前面用到的sql中,如果parameterType="int"
只是进行一个简单的整数传入,但是,在插入语句中,我们传入的是类型User对象:
<insert id="insertUser" parameterType="com.lanhuigu.mybatis.entity.User" useGeneratedKeys="false"> insert into t_user_main (f_id,f_username,f_age) values(#{id},#{username},#{age}) </insert>
如果 User 类型的参数对象传递到了语句中,id、username 和 password 属性将会被查找,然后将它们的值传入预处理语句的参数中。
这点对于向语句中传参是比较好的而且又简单,不过参数映射的功能远不止于此。
首先,像 MyBatis 的其他部分一样,参数也可以指定一个特殊的数据类型。
#{property,javaType=int,jdbcType=NUMERIC}
像 MyBatis 的剩余部分一样,javaType 通常可以从参数对象中来去确定,前提是只要对象不是一个 HashMap。那么 javaType 应该被确定来保证使用正确类型处理器。
NOTE 如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的。你可以自己通过阅读预处理语句的 setNull() 方法的 JavaDocs 文档来研究这种情况。
为了以后定制类型处理方式,你也可以指定一个特殊的类型处理器类(或别名),比如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
尽管看起来配置变得越来越繁琐,但实际上是很少去设置它们。
对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
最后,mode 属性允许你指定 IN,OUT 或 INOUT 参数。如果参数为 OUT 或 INOUT,参数对象属性的真实值将会被改变,就像你在获取输出参数时所期望的那样。如果 mode 为 OUT(或 INOUT),而且 jdbcType 为 CURSOR(也就是 Oracle 的 REFCURSOR),你必须指定一个 resultMap 来映射结果集到参数类型。要注意这里的 javaType 属性是可选的,如果左边的空白是 jdbcType 的 CURSOR 类型,它会自动地被设置为结果集。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支持很多高级的数据类型,比如结构体,但是当注册 out 参数时你必须告诉它语句类型名称。比如(再次提示,在实际中要像这样不能换行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
尽管所有这些强大的选项很多时候你只简单指定属性名,其他的事情 MyBatis 会自己去推断,最多你需要为可能为空的列名指定 jdbcType。
#{firstName} #{middleInitial,jdbcType=VARCHAR} #{lastName}
<insert id="addUser" parameterType="com.lanhuigu.core.dao.model.user.User" useGeneratedKeys="true" keyColumn="f_userId" flushCache="true"> insert into t_user_main ( f_userId , f_userName , f_email , f_password , f_isPayPassword , f_mobile , f_registerTime , f_role , f_status , f_realAuth , f_redInviteCode, f_redBeInviteCode, f_version ) values( #{userId,jdbcType=VARCHAR}, #{userName,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR} , #{isPayPassword,jdbcType=VARCHAR}, #{mobile,jdbcType=VARCHAR}, #{registerTime,jdbcType=VARCHAR}, #{roles,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{realAuth,jdbcType=VARCHAR}, #{redInviteCode,jdbcType=VARCHAR}, #{redBeInviteCode,jdbcType=VARCHAR}, #{version,jdbcType=VARCHAR} ) </insert>
字符串替换
在上面中,很多地方用到#{},默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值(比如?)。
这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串,
或者在条件放一个字符串。比如:
f_id = ${id}
以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,
会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。