mybatis映射文件
mybatis的映射文件使用与书写操作数据库的SQL语句的,下面进行详细的了解
insert标签
参数介绍:
- id:给当前insert标签一个唯一标识
- parameterType:参数类型,可以省略不写,mybatis会根据TypeHandler自动推测
例:
<insert id="setValue" parameterType="com.jinxin.bean.Employee"> INSERT INTO tb1_employee(last_name, gender, email) VALUES(#{lastName}, #{email}, #{gender}) </insert>
#{}里面可以直接写实体类的属性名
测试:
@Test public void test2() throws IOException { String resource = "data/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); try { EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); Employee employee = new Employee(); employee.setGender(0); employee.setLastName("Jerry"); employee.setEmail("123@123.com"); mapper.setValue(employee); session.commit(); } finally { session.close(); } }
注意:
-
此时的sqlSession是不会自动提交的,需要我们手动的commit到数据库,如果想要自动提交,在生成sqlSession的时候传入true参数即可,即sqlSessionFactory.openSession(true)
-
对于增删改,我们可以为方法设置返回,mybatis支持Integer,Boolean(boolean),Long,void等返回值,针对返回值的类型不同会自动封装,如果是Integer跟Long,会返回受影响的行数,如果是Boolean(boolean),则如果受影响的行数大于0,那么就返回false,如果不为0返回true
insert获取自增主键
在JDBC中通过statement的一个方法statement.getGenreateKeys()即可获得主键,在mybatis中同样使用了该方法获取主键,想要获取主键,只需要在insert标签中将useGeneratedKeys设置为true,然后使用keyProperty设置指定对应的主键属性,也就是mybatis获取到主键以后,将这个值封装给javaBean的哪个属性
例:
<insert id="setValue" parameterType="com.jinxin.bean.Employee" useGeneratedKeys="true" keyProperty="id"> INSERT INTO tb1_employee(last_name, gender, email) VALUES(#{lastName}, #{gender}, #{email}) </insert>
不支持自增主键的数据库
针对能够自增的属性,可以使用上面所说的两个属性获取主键,但是对于不支持自增主键的数据库,就不能这样做了,以oracle为例,需要使用序列模拟自增,每次插入值的数据的主键是从序列中拿到的,那么应该怎样在mapper文件中获取呢?
<insert id="setValue" parameterType="com.jinxin.bean.Employee"> <!-- keyProperty:查出的主键封装给javaBean的某个属性 order="BEFORE":当前sql在插入的sql之前运行 "AFTER":当前sql在插入的sql之后运行 resultType:查出的数据的返回值类型,这里的Integer是别名 --> <selectKey keyProperty="id" order="BEFORE" resultType="Integer"> <!-- 编写主键的sql句 --> SELECT EMPLOYEE_SEQ.nextval FROM dual </selectKey> <!-- 插入时的主键是从序列中拿到的 --> INSERT INTO tb1_employee(id, last_name, gender, email) VALUES(#{id}, #{lastName}, #{gender}, #{email}) </insert>
当然,也可以不用先将查到的序列放到javaBean中,直接在SQL中查到序列的下一个值,然后将查询序列的SQL的order设置为AFTER,将当前的序列值放到javaBean中
<insert id="setValue" parameterType="com.jinxin.bean.Employee"> <selectKey keyProperty="id" order="AFTER" resultType="Integer"> <!-- 编写主键的sql句 --> SELECT EMPLOYEE_SEQ.currval FROM dual </selectKey> <!-- 插入时的主键是从序列中拿到的 --> INSERT INTO tb1_employee(id, last_name, gender, email) VALUES(EMPLOYEE_SEQ.nextval, #{lastName}, #{gender}, #{email}) </insert>
update标签
更新所选数据
例:
<update id="updateValue"> UPDATE tb1_employee SET last_name=#{lastName}, email=#{email}, gender=#{email} WHERE id=#{id} </update>
delete标签
删除所选数据
例:
<delete id="deleteValue"> DELETE FROM tb1_employee WHERE id=#{id]} </delete>
select标签
属性介绍
- id:给标签一个唯一的标识
- resultTye:返回值类型
- resultMap:自定义返回值类型
例:
<select id="getById" resultType="com.jinxin.bean.Employee"> SELECT * FROM `tb1_employee` WHERE id = #{id} </select>
返回结果处理
从数据库查询的结果可能多种多样,需要我们用不同的方式去接收,有些甚至需要自定义接收规则(resultMap)
下面一一列举
select返回list
如果当查询的结果是多条数据,就可以使用List接收返回的结果,但是这里的resultType并不是list,而是集合里面储存的元素的类型,例如,这是一条查询多个员工的语句,那么resultType里面写的应该是员工对应的实体类,而并非list
例:
<select id=”getEmpByLastName” resultType=”com.jinxin.bean.Employee”> SELECT * FROM tb1_employee WHERE last_name LIKE #{lastName} </select>
select返回map
使用map储存数据的情况分为两种。一种是查询的结果只有一条,但是将这一条数据的每一列的列名跟值都以键值对的情况去保存在map中。第二种是查询的结果是多条,每一条数据为一个单位存在于value中,至于键的名字,以某一列的值做为键,一般是id,因为map的键名不能重复
单条数据封装成map
只需要将resultType的只设置为map即可
例:
<select id=”getEmpByIdReturnMap” resultType=”map”> SELECT * FROM tb1_employee WHERE id=#{id} </select>
多条数据封装成map
这时需要改变的是mapper接口的方法,返回值类型为 Map<String, Employee> ,而在select标签上面的resultType仍然书写为对应实体类的全类名
即:
<select id=”getEmpByLastName” resultType=”com.jinxin.bean.Employee”> SELECT * FROM tb1_employee FROM WHERE last_name LIKE #{lastName} </select>
但是还有一点,还需要在mapper接口的方法上标注上@MapKey注解,指明以那一列的数值作为键,如:
@MapKey(“id”) Map<Integer, Employee> getEmpByLastName(String lastName);
就是以id的值作为键
使用resultMap自定义结果集映射规则
有的时候,对于返回的结果并不是一个实体类或者map能够全部接收搞定的,它们更加的复杂,往往需要我们自定义结果集的映射规则,而自定义结果集映射规则用到的一个很重要的标签就是resultMap
这里先举一个小例子说明resultMap的好处,例如如果表的列名跟实体类的属性名不能够完美的匹配,那么可以使用resultMap来做一一映射
例:

<mapper namespace="com.jinxin.mapper.EmployeeMapperPlus"> <!-- 自定义某个实体类的封装规则 type:实体类名 id:给一个唯一标识 --> <resultMap id="MyEmp" type="com.jx.bean.Employee"> <!-- 指定主键的封装规则,也可以使用result定义主键,只是使用id定义主键mybatis会在底层有优化 column:指定表的列名 property:指定实体类对应的属性名 --> <id column="id" property="id" /> <!-- 定义普通列的封装规则 --> <result column="last_name" property="lastName" /> <!-- 其他不指定的列会自定封装(列名跟属性名相同),但是推荐只要定义了resultType就把所有的对应关系写上 --> <result column="email" property="email" /> <result column="gender" property="gender" /> </resultMap> <select id="getEmpById" resultMap="MyEmp"> SELECT * FROM tb1_employee WHERE id=#{id} </select> </mapper>
当然了,这里只是一个小例子,并不能体现resultMap真正的强大之处。resultMap真正的强大之处在于能够处理外键关联的对象,试想,如果一个实体类中有一个属性是另外一个实体类,但是在表中只是以外键的形式将两张表进行关联,也就是表中与这个实体类属性关联的列只是一个外键,一个数字,那么这时候用简单点的resultType处理肯定就不合适了,一个数字这么赋值给一个实体类对象?
回到这个问题,如果一个实体类中有一个关联的实体类,那么怎么赋值?首先明确应该怎样发送SQL查询,第一种方式可以使用连表查询将所有的数据一起查出来。还有一种方式,先查出一条数据,获取外键,然后去关联的表查询与这条外键对应的列
首先说说连表查询怎样封装resultMap
处理下面这个场景
如果每个员工都对应一个部门,现在要查询每个员工的同时查询员工对应的部门信息
<resultMap id="MyDifEmp" type="com.jinxin.bean.Employee"> <id column="eid" property="id" /> <result column="last_name" property="lastName" /> <result column="gender" property="gender" /> <result column="email" property="email" /> <result column="fk" property="department.id" /> <result column="dept_name" property="department.departmentName" /> </resultMap> <select id="getEmpAndDept" resultMap="MyDifEmp"> SELECT e.id eid, e.last_name last_name, e.gender gender, e.email email, e.d_id fk, d.id did, d.dept_name dept_name FROM tb1_employee e INNER JOIN tb1_dept d ON e.d_id = d.id WHERE
e.id=#{id} </select>
association
上面封装另外一个实体类的时候,使用的是 属性.属性 的方式对department里面的id跟dedpartmentName进行赋值,其实还有一种方式,便是通过association赋值
例:
<resultMap id="MyDifEmp2" type="com.jinxin.bean.Employee"> <id column="eid" property="id" /> <result column="last_name" property="lastName" /> <result column="gender" property="gender" /> <result column="email" property="email" /> <!-- property:指定关联的另外一个实体类 javaType:指定该实体类的类型[不能省略] --> <association property="department" javaType="com.jinxin.bean.Department"> <id column="did" property="id" /> <result column="dept_name" property="departmentName" /> </association> </resultMap> <select id="getEmpAndDept" resultMap="MyDifEmp"> SELECT e.id eid, e.last_name last_name, e.gender gender, e.email email, e.d_id fk, d.id did, d.dept_name dept_name FROM tb1_employee e INNER JOIN tb1_dept d ON e.d_id = d.id WHERE e.id=#{id} </select>
无论是使用这种属性链属性的方式封装还是使用association的方式封装都是使用连表查询先查询出两张表的所有数据,然后再对实体类进行一一对应的赋值,那么接下来介绍另外一种方式,先查询一张表的数据,再根据其外键查询另外一张表的数据
使用association进行分布查询
首先明确分步查询的步骤:
- 先按照员工id查询员工信息
- 再按照员工信息中的d_id外键查询员工对应的部门信息
- 将查询到的部门信息放置到员工信息中
Okay,那么首先明确resultMap封装流程,首先还是通过id跟result等标签封装当前实体类的普通属性,然后在association中,如先前一样,还是在里面使用id和result标签封装另外一个实体类相关的属性,但是这里多了一步,就是需要通过外键先查询,而且这个查询的方法已经在另一个实体类对应的mapper接口中定义好了,现在要做的只是拿到这个方法,而且将主键值作为参数传递给这个方法去执行,后面association就能帮我们自动封装查询出来的方法了。那么完成拿到这个方法这一任务的属性就是select,而完成将外键值传给方法这一任务的属性就是column。那么现在看一个栗子
<resultMap id="getEmp" type="com.jinxin.bean.Employee"> <id column="id" property="id" /> <result column="last_name" property="lastName" /> <result column="email" property="email" /> <result column="gender" property="gender" /> <!-- 定义关联对象的封装规则 select:因为要在这里面通过外键查询另一张表的数据,所以要指定通过外键查询的方法 column:给select中指定的方法传入参数,即外键值 流程:使用select属性指定的mapper接口的方法(参数是column对应的值)查出对象,并封装给property指定的属性 --> <association property="department" select="com.jinxin.mapper.DepartmentMapper.getDeptById" column="d_id"> </association> </resultMap> <select id="getEmpAndDeptStep" resultMap="getEmp"> SELECT * FROM tb1_employee WHERE id=#{id} </select>
分布查询之懒加载
上面的查询每次进行查询都会去数据库一次性查询到所有的信息,为了减轻数据库的压力,我们可以使用延迟加载,在每次用到的时候再去查询数据库拿到需要的信息,即按需加载。
使用延迟加载非常简单,只需要在分步查询的基础上在主配置文件中加上两个setting配置即可
<settings> <setting name="lazyLoadingEnabled" value="true" /> <setting name="aggressiveLazyLoading" value="false" /> </settings>
在全局设置了懒加载就意味着所有的查询都是懒加载,如果我们在某一项查询中不需要使用懒加载可以使用fetchType覆盖掉全局的设置,它有两个值,默认是lazy(延迟加载),跟eager(立即加载)
collection
其实上面说了这么多,本质上是在使用resultMap封装一对多。那么上面已经解决了一对多多的一方的封装,下面自然就要解决一对多一的一方的封装了。但是一的一方持有的不再是单个实体类了,而是所有与之关联的实体类的集合,用List保存,那么在resultMap中又应该用什么方式封装这个List结果集呢?根据标题可以知道,使用collection,下面就具体介绍这个collection的用法
现在有这样一个场景,根据某个id查询出一条部门信息,并且查询出所有与该id进行外键关联了的员工
很明显,同样有两种方式去做,一种是通过连表查询,直接查出与该id相等的外键有哪些。第二种方式是先查出这个部门,然后根据这个部门去员工表中匹配外,拿到所有属于该部门的员工信息
首先还是说说连表查询的封装规则
其实同association的封装规则很相似,collection里面还是用id跟result封装普通的属性,仍然是用property指定属性名,使用column指定列名。对于collection的属性,有一个ofType是用来指定List里面储存的实体类的类型。property用来指定这个列表的名字
如下:
<resultMap id="getDept" type="com.jinxin.bean.Department"> <id column="did" property="id" /> <result column="dept_name" property="departmentName" /> <!-- collection定义关联集合类型的属性的封装规则 ofType:指定集合里面的实体类的类型 --> <collection property="emps" ofType="com.jinxin.bean.Employee"> <!-- 定义集合中的实体类的封装规则 --> <id column="eid" property="id" /> <result column="last_name" property="lastName" /> <result column="email" property="email" /> <result column="gender" property="gender" /> </collection> </resultMap> <select id="getDeptByIdPlus" resultMap="getDept"> SELECT d.id did, d.dept_name dept_name, e.id eid, e.last_name last_name, e.email email, e.gender gender FROM tb1_dept d LEFT JOIN tb1_employee e ON d.id=e.d_id WHERE d.id=#{id} </select>
使用collection进行分布查询
上面说了连表查询的方式,下面使用分布查询的方式查询数据
select属性同样是传入已经写好的通过外键查员工的方法,column同样是将部门的id传给该方法
<resultMap id="getDept2" type="com.jinxin.bean.Department"> <id column="id" property="id" /> <result column="dept_name" property="departmentName" /> <collection property="emps" select="com.jinxin.mapper.EmployeeMapperPlus.getEmpByDeptId" column="id"></collection> </resultMap> <select id="getDeptAndEmpStep" resultMap="getDept2"> SELECT * FROM tb1_dept WHERE id=#{id} </select>
懒加载
collection的懒加载设置跟association的一致
扩展:association跟collection传入多个值
上面的分步查询使用到的方法都只需要传递一个值,那么如果涉及到的方法要传入多个值怎么办呢?
如果要传递的值是多个,可以通过map传递,column=”{key1=column1, key2=column2}”,即column=”{deptId=id}”
discriminator
鉴别器,mybatis可以使用鉴别器判断某列的值,然后根据这一列的值改变封装行为
例:
封装Employee:
要求:如果查询的是女生,就把其部门信息查询出来,否则不查询其部门信息,而且如果是男生,就把last_name这一列赋值给email
<resultMap id="getEmp" type="com.jinxin.bean.Employee"> <id column="id" property="id" /> <result column="last_name" property="lastName" /> <result column="email" property="email" /> <result column="gender" property="gender" /> <!-- column:指定判断的列的列名 javaType:列对应的java类型 --> <discriminator javaType="string" column="gender"> <!-- 女生 resultType(必须):指定封装结果的类型,也可以指定resultMap --> <case value="0" resultType="com.jinxin.bean.Employee"> <association property="department" select="com.jinxin.mapper.DepartmentMapper.getDeptById" column="d_id"> </association> </case> <!-- 男生 --> <case value="1" resultType="com.jinxin.bean.Employee"> <id column="id" property="id" /> <result column="last_name" property="lastName" /> <result column="last_name" property="email" /> <result column="gender" property="gender" /> </case> </discriminator> </resultMap> <select id="getEmpAndDeptStep" resultMap="getEmp"> SELECT * FROM tb1_employee WHERE id=#{id} </select>
mapper接口方法的参数处理
单个参数
对于单个参数,mybatis不会做特殊处理,使用 #{参数名] 即可取出参数值
多个参数
mybatis在操作多个参数时会做特殊的处理,多个参数会被封装到一个map里面,也就是{param1: id, param2: lastName}的形式
#{} 也是从map中通过key获取相应的值,也就是说如果传入的是多个参数,要使用#{param1}的形式才能取到值,或者使用参数的索引也可以取到值,例如 #{0}
命名参数
如果按照上面使用param1、param2...进行取值确实不方便,不但low,而且参数之间也不便于区分,这时可以使用命名参数显示的指定key的值,也就是将param1的换成自定义的名字
而自定义命名参数的方法也很简单,只需要在对应的方法前面加上@Param注解即可
Employee getByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
这样就可以使用 #{id} 跟 #{lastName} 取值了
使用POJO & Map & TO(Transfer Object)
POJO
如果多个参数正好是我们业务逻辑中的数据模型,那我们就可以直接传入相应的pojo
使用#{属性名}即可取出相应pojo的属性值
Map
如果多个参数不是业务模型中的数据,没有对应的pojo,且不经常使用,那么为了方便也可以传入map,使用#{key}即可取出map中的值
To
如果多个数据不是业务逻辑中的数据模型,且要经常使用,那么推荐编写一个TO(Transfer Object)数据传输对象,例如查分页的Page对象
特别注意
如果封装的对象是一个Collection(List, Set)类型或者数组,也会进行处理,也就是把传入的值放到map中,如果是Collection,那么key就是collection,如果是List除了使用collection以外还可以使用list,如果是数组,key就是array
即:Employee getById(List<Integer> ids);
取值:#{list[0]}
参数取值
除了可以使用#{}取值以外,还可以使用${}取值,那么这两种取值方式有什么差别呢?其实在取值的时候还真没有什么差别,唯一的差别就是:
#{}是以预编译的方式将参数设置到sql中,即相当于JDBC中的PreparedStatement
${}取出的值直接拼装在sql语句中,有安全问题,因此大多数情况下应该使用#{}取值
但是在jdbc不支持预编译的地方可以使用${}进行取值,例如我想要查找的表是动态的,即有2016_salary、2017_salary等等,那么就可以使用${year}_salary进行取值
#{}更丰富的用法
规定参数的一些规则:
javaType、jdbcType、mode(存储过程)、numericScale、resultMap、typeHandler、jdbcTypeName、expression(未来准备支持的功能)
jdbcType通常需要在某种特定的条件下被设置,有些数据库可能不能识别mybatis对null的默认处理,比如oracle(报错),如果想要保存一个值为null的字段会报错(jdbcType OTHER),因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理
解决这种错误的方法有两种
1、对该插入为null的字段指定jdbcType:#{email, jdbcType=NULL}
2、更改全局配置jdbcTypeForNull = NULL <setting name=”jdbcTypeForNull” value=”NULL” />