Fork me on GitHub

mybatis学习笔记

mybatis(持久层框架)

mybatis让程序员将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。
mybatis可以将向preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象(HashMap,JavaBean,基本数据类型)。(输出映射)

sqlMaoConfig.xml

sqlMaoConfig.xml(是mybatis的全局配置文件,名称是不固定的)配置了数据源,事务等mybatis运行环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 和Spring整合后environments配置将废除 -->
<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>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<mapper resource="config/User.xml"/>
<!-- 通过resource方法,一次加载一个映射文件 -->
<mapper resource="config/UserMapper.xml"/>
<mapper resource="config/OrdersMapperCustom.xml"/>
</mappers>

映射文件

根据数据表编写实体类,然后配置映射文件(n个)(配置sql语句)
mapper.xml(映射文件)mapper.xml,mapper.xml
User.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<mapper namespace="test">
<select id="findUserById" parameterType="int" resultType="mybatis.po.User">
select * from user where id = #{id}
</select>
<select id="findUserByName" parameterType="java.lang.String" resultType="mybatis.po.User">
select * from user where username like '%${value}%'
</select>
<insert id="insertUser" parameterType="mybatis.po.User">
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into user (id,username,birthday,sex,address)value(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
</delete>
<update id="updateUser" parameterType="mybatis.po.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}
</update>
</mapper>

UserMapper.xml:

1
2
3
4
5
6
<mapper namespace="mybatis.mapper.UserMapper">
<cache/>
<select id="findUserById" parameterType="int" resultType="mybatis.po.User">
select * from user where id = #{id}
</select>
</mapper>

SqlSessionFactory(会话工厂)

作用:创建SqlSession
dao开发方法:

1
2
3
4
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("test.deleteUser",id);
User user = sqlSession.selectOne("test.findUserById",id);
sqlSession.insert("test.insertUser", user);

mapper代理开发方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package mybatis.mapper;
public interface UserMapper {
//根据id查询用户信息,抛异常有利于健壮性
public User findUserById(int id)throws Exception;
//用户信息综合查询
public List<UserCustom> findUserList(UserQueryVo userQueryVo)throws Exception;
//用户信息综合查询总数
public int findUserCount(UserQueryVo userQueryVo)throws Exception;
//根据id查询用户信息,使用resultMap输出
public User findUserByIdResultMap(int id) throws Exception;
//根据用户名列查询用户列表
public List<User> findUserByName(String name)throws Exception;
//添加用户信息
public void insertUser(User user)throws Exception;
//删除用户信息
public void deleteUser(int id) throws Exception;
//更新用户
public void updateUser(User user)throws Exception;
}
1
2
3
4
5
6
7
8
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper的方法
User user = userMapper.findUserById(1);
//实现分页查询;offset起始行limit;记录数量
RowBounds bounds = new RowBounds(offset,limit);
sqlSession.selectList(SQLID,参数,RowBounds对象);

使用注解开发CRUD(数据库操作)

大量的XML配置文件的编写是非常烦人的。因此Mybatis也提供了基于注解的配置方式,下面我们来演示一下使用接口加注解来实现CRUD的的例子。

通过注解配置Mapper

首先是创建一个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.bird.mybatis.bean;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface UserMapper {
@Insert("insert into users(name, age) values(#{name}, #{age})")
public int add(Users user);
@Delete("delete from users where id = #{id}")
public int deleteById(int id);
@Update("update users set name = #{name}, age = #{age} where id = #{id}")
public int update(Users user);
@Select("select * from users where id = #{id}")
public Users getUserById(int id);
@Select("select * from users")
public List<Users> getAllUsers();
}

然后一定不要忘了在conf.xml配置文件中,注册这个类

1
2
3
4
<mappers>
<mapper resource="com/bird/mybatis/bean/userMapper.xml" />
<mapper class="com.bird.mybatis.bean.UserMapper"/>
</mappers>

使用这个类:

1
2
3
4
5
6
7
8
@Test
public void testAdd() {
SqlSession openSession = factory.openSession();
UserMapper mapper = openSession.getMapper(UserMapper.class);
mapper.add(new Users(-1,"娃娃",99));
openSession.commit();
openSession.close();
}

通过方法生成sql语句

1
2
@SelectProvider(type = TestSqlProvider.class, method = "findCount")
Long findCount(@Param("Id") int Id, @Param("type") String type);

使用 @SelectProvider 注解可以执行type中的类(这里是TestSqlProvider)的方法(这里是findCount)。方法的参数一般是Map map。这里的 @Param 会自动映射到map中(例如: @Param(“Id”) int Id:map的key为Id,value为参数Id的值)。
TestSqlProvider中的方法的返回值类型为String,返回值为生成的sql语句。例如:

1
2
3
4
5
6
public String findCount(Map<String, Object> map) {
int Id = (Integer)map.get("Id");
String type = map.get("type").toString();
String sql = "SELECT count(*) FROM emp WHERE id=" + Id + " and type = "+ type;
return sql;
}

SqlSession(会话)

SqlSession(会话),是一个接口,面向用户(程序员)的接口
作用:操作数据库(发出sql增删改查)

Executor(执行器)

Executor(执行器),是一个接口(基本执行器,缓存执行器)
作用:SqlSession内部通过执行器操作数据库

mapped statement(底层封装对象)

作用:对操作数据库存储封装,包括sql语句,输入参数,输出结果类型

动态sql

与JSTL相似,允许我们在XML中构建不同的SQL语句
判断元素:if,choose
关键字元素:where:简化查询语句中where部分的条件判断。
set:用在更新操作的时候,作用和where相似;trim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<select id="findByCondition" parameterType="Emp" resultType="Emp">
select * from EMP
<where>
<if test="deptno!=null">
DEPTNO=#{deptno}
</if>
<choose>
<when test="sal>2000">
and SAL>=#{sal}
</when>
<otherwise>
and SAL>=2000
</otherwise>
</choose>
</where>
</select>
<update id="updateEmp" parameterType="Emp">
update EMP
<set>
<if test="ename!=null">
ENAME=#{ename},
</if>
<if test="deptno!=null">
DEPTNO=#{deptno}
</if>
</set>
where EMPNO=#{empno}
</update>

循环元素:foreach:实现了循环逻辑,可以进行一个集合的迭代

1
2
3
4
5
6
7
8
9
<select id="findByDeptNos" resultType="Emp" parameterType="Emp">
select * from EMP
<if test="deptnos!=null">
where DEPTNO in
<foreach collection="deptnos" item="dno" open="(" close=")" separator=",">
#{dno}
</foreach>
</if>
</select>

关系映射

主键自动递增:MySQL、SQLServer数据库都支持字段自动递增功能,在使用MyBatis时,有时要返回MySQL、SQLServer数据库自动增长的主键值

1
2
3
<insert id="addDept" parameterType="Dept" keyProperty="deptno" useGeneratedKeys="true">
insert into T_DEPT (DNAME,LOC)values(#{dname},#{loc})
</insert>

元素指定了自动激增属性设置后,MyBatis会在插入操作后将自动生成的主键值给keyProperty指定的属性赋值
非自动递增:Oracle不支持自动递增,采用序列生成主键值

1
2
3
4
5
6
<insert id="addDept" parameterType="Dept">
<selectKey keyProperty="deptno" order="before" resultType="java.lang.Integer">
select dept_seq.nextval from dual
</selectKey>
insert into T_DEPT (DEPTNO,DNAME,LOC)values(#{deptno},#{dname},#{loc})
</insert>

resultMap使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 订单查询关联用户的resultMap
将整个查询的结果映射到mybatis.po.Orders中-->
<resultMap type="mybatis.po.Orders" id="OrderUserResultMap">
<!-- 配置映射的订单信息 -->
<!-- id:指定查询列中的唯一标识,订单信息中的唯一标识
如果由多个列组成唯一标识,配置多个id -->
<id column="id" property="id"></id>
<result column="user_id" property="userId"></result>
<result column="unumber" property="number"></result>
<result column="createtime" property="createtime"></result>
<result column="note" property="note"></result>
<!-- 配置映射的关联的用户信息 -->
<!-- association:用于映射关联查询单个对象的信息
property:要将关联查询的用户信息映射到Orders中哪个属性 -->
<association property="user" javaType="mybatis.po.User">
<!-- id:关联查询用户的唯一标识
column:指定唯一标识用户信息的列
JavaType:映射到user的哪个属性 -->
<id column="user_id" property="id"></id>
<result column="username" property="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
</association>
</resultMap>
<select id="findOrdersUserResultMap" resultMap="OrderUserResultMap">
select orders.*,user.username,user.sex,user.address from orders,user where orders.user_id=user.id
</select>

关联映射作用:获取两个或以上关联表的数据
嵌套查询映射(查两次):

1
2
3
4
5
6
7
8
9
10
11
<select id="findById" parameterType="java.lang.Integer" resultMap="empMap">
select * from EMP where EMPNO=#{id}
</select>
<select id="selectDept" parameterType="java.lang.Integer" resultType="Dept">
select * from DEPT where DEPTNO=#{id}
</select>
//如果查询字段名和Java POJO属性不一致是,需要使用<resultMap>元素显式指定映射关系。
//从selectDept得到的结果,将字段名DEPTNO映射到dept属性上。
<resultMap type="Emp" id="empMap">
<association property="dept" column="DEPTNO" javaType="Dept" select="selectDept" />
</resultMap>

嵌套结果映射(查一次):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findById" parameterType="java.lang.Integer" resultMap="empMap">
select e.empno,e.ename,e.job,e.mgr,e.sal,e.comm,d.dname,d.loc from EMP e join DEPT d on(d.deptno=e.deptno)
where e.EMPNO=#{id}
</select>
<resultMap type="Emp" id="empMap">
<id column="EMPNO" property="empno"></id>
<result column="ENAME" property="ename"></result>
<result column="JOB" property="job"></result>
<result column="SAL" property="sal"></result>
<result column="COMM" property="comm"></result>
<association property="dept" javaType="Dept">
<id column="DEPTNO" property="deptno"></id>
<result column="DNAME" property="dname"></result>
<result column="LOC" property="loc"></result>
</association>
</resultMap>

mybatis开发dao两种方式:

原始dao开发方法(程序需要编写dao接口和dao实现类)(掌握)
mybatis的mapper接口(相当于dao接口)代理开发方法(掌握)
mybatis配置文件SqlMapConfig.xml
mybatis核心:
mybatis输入映射(掌握)parameterType:指定输入参数类型可以简单类型、pojo、hashmap。。
mybatis输出映射(掌握)resultType,resultMap
mybatis的动态sql(掌握)

高级知识:
订单商品数据模型分析
高级结果集映射(一对一,一对多,多对多)
mybatis延迟加载
mybatis查询缓存(一级缓存,二级缓存)
mybatis和spring进行整合(掌握)
mybatis逆向工程

mybatis和hibernate本质区别和应用场景:

hiberbate:是一个标准ORM框架(对象关系映射)。入门门槛较高的,不需要程序员写sql,sql语句自动生成了。
对sql语句进行优化、修改比较困难的。
应用场景:适用于需求变化不多的中小型项目,比如说:后台管理系统,erp,orm,oa….
mybatis:专注是sql本身,需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全的ORM框架,
虽然程序员自己写sql,mybatis也可以实现映射(输入映射,输出映射)。使用于需求变化较多的项目,比如:互联网项目。
企业进行技术选型:以低成本 高回报作为技术选型的原则,根据项目组的技术力量进行选择。
说白点,hibernate不需要写sql,mybatis需要写sql

Spring与MyBatis整合:

SqlSessionFactoryBean:为整合应用提供SqlSession对象;在applicationContext.xml中定义格式如下:

1
2
3
4
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mapperLocations" value="classpath:org/yh/entity/*.xml"/>
</bean>

MapperFactoryBean:根据Mapper接口获取我们想要的Mapper对象

1
2
3
4
5
6
7
8
9
10
11
<bean id="deptMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!--指定Mapper接口,用来返回Mapper对象 -->
<property name="mapperInterface" value="org.yh.mapper.DeptMapper" />
<!--指定SqlSessionFactoryBean对象,用于提供SqlSession -->
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
MapperScannerConfigurer:通过这个组件会自动扫描各个Mapper接口,并注册对应的MapperFactoryBean对象
basePackage用于指定Mapper接口所在的包,将Mapper接口注册为对象,多个包之间可以用逗号或者分号分隔。
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.yh.mapper" />
</bean>

基于SqlSessionTemplate的DAO配置信息:

1
2
3
4
5
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<!--扫描DAO并注入template,可以用注解方式实现 -->
<context:component-scan base-package="org.yh.dao" />

DAO实现与之前单独MyBatis一致

提升

通过一个方法进行多种查询

只要方法签名相同,返回值类型相同。即可通过一个方法进行多种查询。
方法签名:方法名和参数
例如:一个查询数量的Mapper方法,返回值是int,那么其实可以进行多种查询。可以写成一个通用的数量查询。
Mapper接口方法:

1
2
@SelectProvider(type = TestSqlProvider.class, method = "findCount")
Long findCount(@Param("Id") int Id, @Param("type") String type);

这个方法(findCount)通过调用TestSqlProvider类中的findCount方法来得到具体的sql语句。其中TestSqlProvider类中的findCount方法的参数是Map map;可以自动将Mapper接口中的findCount方法的参数放到map中(比如@Param(“Id”)中的Id为map的key,Id属性值为value)。

TestSqlProvider类中的findCount方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 根据条件进行数量查找
* @param map
* @return
*/
public String findCount(Map<String, Object> map) {
int Id = (Integer)map.get("Id");
String type = map.get("type").toString();
String sql = new String();
if("applyToMe".equals(type)){
sql = "SELECT count(*) FROM emp WHERE " + "id=" + Id ";
}else if("application".equals(type)){
sql = "SELECT count(*) FROM emp1 WHERE " + "id=" + Id ";
}else if("applyToMeUndone".equals(type)){
sql = "SELECT count(*) FROM emp2 WHERE " + "id=" + Id ";
}else if("applicationUndone".equals(type)){
sql = "SELECT count(*) FROM emp3 WHERE " + "id=" + Id ";
}else{
sql = "SELECT count(*) FROM emp WHERE 1=2";
}
return sql;
}

这个方法返回了一个sql语句。可以根据不同的type值进行不同的sql返回。如果参数值是条件里没有的,返回的sql中有1=2的条件,即查询结果为0。
该方法相当于一个通用的数量查询方法。

下划线与驼峰式命名的映射

mybatis支持属性使用驼峰的命名
需要开启一个配置
mybatis-config.xml:

1
2
3
4
5
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>

mapUnderscoreToCamelCase:是否启用下划线与驼峰式命名规则的映射(如first_name => firstName;数据库字段是first_name,entity变量是firstName
开启了这个开关()
mapper配置不需要写字段与属性的配置,会自动映射。
注:需要Mybatis-3.3.0jar包和Mybatis-spring-1.1.1jar包支持

mybatis的sql防注入

在mybatis中,”${xxx}”这样格式的参数会直接参与sql编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“${xxx}”这样的参数格式,所以,这样的参数需要我们在代码中手工进行处理来防止注入。
在编写mybatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。
使用模糊查询时,如果要做到sql防注入,采用“#{xxx}”这样的格式的话,拼接%需要用到CONCAT。

1
2
3
4
//mysql用CONCAT拼接字符串
AND c.name like CONCAT(CONCAT('%', #{name}), '%')
//Oracle可以用||拼接字符串
AND c.name like '%'||#{name}||'%'

参考文档

请点击参考文档

「真诚赞赏,手留余香」