一.MyBatis简介
a.简介
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL , 存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java的 POJO(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录
每个MyBatis应用程序主要利用SqlSessionFactory实例操作数据库,而SqlSessionFactory实例可以通过
SqlSessionFactoryBuilder获得.SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类实例获得用xml文件构建SqlSessionFactory实例很简单,文件最好放在resource包下,(但你可以使用任何Reader实例,包括用文件路径或者f://开头的url创建实例).MyBatis有一个实用类(Resources),它有很多方法,可以方便地从类路径以及其他位置加载资源。
b.框架特点
简单
灵活
解除sql与程序代码的耦合:通过提供DAL层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试. sql和代码的分离,提高了可维护性。
提供映射标签,支持对象与数据库的orm字段关系映射
提供对象关系映射标签,支持对象关系组建维护
提供xml标签,支持编写动态sql
c.总体流程
-
加载配置并初始化
- 触发条件:加载配置文件
- 处理过程:将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中.
-
接收调用请求
- 触发条件:调用Mybatis提供的API
- 传入参数:为SQL的ID和传入参数对象
- 处理过程:将请求传递给下层的请求处理层进行处理
-
处理操作请求
- 触发条件:API接口层传递请求过来
- 传入参数:为SQL的ID和传入参数对象
- 处理过程
- 根据SQL的ID查找对应的MappedStatement对象
- 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数
- 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果
- 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果
- 释放连接资源
d.MyBatis功能架构分层
- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑
e.MyBatis和数据交互的方式
-
共两种方式 :
- 使用传统的MyBatis提供的API
- 使用Mapper接口
-
传统的MyBatisAPI
- 通过调用MyBatis中SqlSession对象的方法从而达到与数据库交互的方式,有一些类似DBUtils的操作
- 创建一个和数据库打交道的SqlSession对象,然后根据Statement Id 和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。
-
使用Mapper接口
- 由于面向接口的
编程是面向对象的大趋势,MyBatis为了适应这一趋势,增加了使用MyBatis 支持接口(Interface)调用方式 - MyBatis将核心配置文件中的每一个节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟节点中的<select|update|delete|insert>节点项对应,即<select|update|delete|insert> 节点的id值为Mapper接口中的方法名称,parameterType值表示Mapper对应方法的入参类型,而resultMap 值则对应了Mapper接口表示的返回值类型或者返回结果集的元素类型
- 由于面向接口的
f.配置文件mybatis.xml
- 可以配置当前环境信息,加载映射文件,加载properties文件,配置全局参数,定义别名等。
<?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>
<!--引入外部配置文件db.properties-->
<properties resource="db.properties" />
<!--给当前mybatis项目添加日志功能,该STDOUT_LOGGING值的好处是不用添加第三方jar包就可以有日志输出-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--配置mybatis环境变量-->
<environments default="development">
<environment id="development">
<!--配置JDBC事务控制,由mybatis进行管理-->
<transactionManager type="JDBC"/>
<!--配置数据源,采用mybatis连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${pass}"/>
</dataSource>
</environment>
</environments>
<!--加载映射文件-->
<mappers>
<!--使用资源的路径-->
<mapper resource="mapper/AddressMapper.xml" />
<mapper resource="mapper/DetailsMapper.xml" />
<!-- <mapper resource="com/mybrtis/mapper/UserMapper.xml"/> -->
<!-- 使用资源的绝对路径<mapper url=""/> -->
<!--
Mapper接口的全类名
要求:Mapper接口的名称与映射文件名称一致
</configuration>必须在同一个目录下
<mapper class="com.qf.mapper.UserMapper"/>
-->
<!-- 加载某个包下的映射文件 (推荐)
要求:Mapper接口的名称与映射文件名称一致
必须在同一个目录下
-->
</mappers>
</configuration>
g.映射文件xxxMapper.xml
- 在指定的目录下创建映射文件,配置要执行的statement,即增删改查等语句
<?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">
<!--
namespace:配置名称空间,对配置的statement进行分类管理
此时名称可以任意
当使用Mapper代理时,namespace具有特殊的含义与功能
-->
<mapper namespace="pojo.UserMapper">
<!--
根据id查询用户,User findById(int id)
select:配置查询语句
id:可以通过id找到执行的statement,statement唯一标识
parameterType:输入参数类型
resultType:输出结果类型
#{}:相当于占位符
#{id}:其中的id可以表示输入参数的名称,如果是简单类型名称可以任意
-->
<!--查询所有的用户信息-->
<select id="selectAllUser" resultType="com.mybatis.pojo.UserInfo">
select * from userinfo;
</select>
<!--根据用户信息数量-->
<select id="selectUserNum" resultType="int">
select count(1) from userinfo;
</select>
<!--根据id查询用户信息-->
<select id="selectOneUser" resultType="com.mybatis.pojo.UserInfo">
select * from userinfo where id=#{uid};
</select>
<!--limit查询几条数据-->
<select id="selectUserPage" resultType="com.mybatis.pojo.UserInfo">
select * from userinfo where sex=#{sex} limit #{startPage}, #{pagesize};
</select>
<!--新增用户信息-->
<!-- 添加用户
#{user}:名称与类中的属性名一致,可以使用对象接收,也可使用map接收
-->
<insert id="insertUser">
insert into userinfo values (default ,#{userId},#{realName},#{sex},#{email},#{phone},default ,#{address});
</insert>
<!--根据用户id更新用户信息-->
<update id="updataUserById">
update userinfo SET realName=#{realName} WHERE id=#{id};
</update>
<!--根据id删除用户-->
<delete id="deleteUserById">
delete from userinfo where id=#{uid};
</delete>
</mapper>
二.MyBatis的简单使用
a.获取SqlSession对象
- MyBatis框架中涉及到的几个API
- SqlSessionFactoryBuilder:该对象负责根据MyBatis配置文件SqlMapConfig.xml构建SqlSessionFactory实例
- SqlSessionFactory:每一个MyBatis的应用程序都以一个SqlSessionFactory对象为核心。该对象负责创建SqlSession对象实例。
- SqlSession:该对象包含了所有执行SQL操作的方法,用于执行已映射的SQL语句
//1、读取配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//2、根据配置文件创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3、SqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//或者
private SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml");
private SqlSession ss=ssf.openSession(true); //设置自动提交
b.利用SqlSession实现CRUD操作
- 新增操作
- 在映射文件配置标签,用于执行插入操作。
- 在插入操作完成之前或之后,可以配置标签获得生成的主键的值,获得插入之前还是之后的值,可以通过配置order属性来指定。
- LAST_INSERT_ID:该函数是mysql的函数,获取自增主键的ID,它必须配合insert语句一起使用
<!-- 添加用户 -->
<!-- selectKey:查询主键,在标签内需要输入查询主键的sql -->
<!-- order:指定查询主键的sql和insert语句的执行顺序,相当于insert语句来说 -->
<!-- LAST_INSERT_ID:该函数是mysql的函数,获取自增主键的ID,它必须配合insert语句一起使用 -->
<insert id="addUser" parameterType="com.meng.po.User”>
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO USER(username,birthday,sex,address)
VALUES(#{username},#{birthday},#{sex},#{address})
</insert>
或者
<insert id="insertUser">
insert into userinfo values (default ,#{userId},#{realName},#{sex},#{email},#{phone},default ,#{address});
</insert>
- 删除操作
- 在映射文件文件中使用标签配置删除的statement
<delete id="deleteUserById">
delete from userinfo where id=#{uid};
</delete>
- 修改操作
- 在映射文件使用标签配置修改的statement
<update id="updataUserById">
update userinfo SET realName=#{realName} WHERE id=#{id};
</update>
- 查询操作
- 在映射文件配置< select> 标签执行查询操作
- 注意
- {}:相当于占位符
- {id}:其中的id可以表示输入参数的名称,如果是简单类型名称可以任意
- ${}:表示拼接sql语句
- ${value}:表示输入参数的名称,如果参数是简单类型,参数名称必须是value
- {}:相当于占位符
<!--
根据id查询用户,User findById(int id)
select:配置查询语句
id:可以通过id找到执行的statement,statement唯一标识
parameterType:输入参数类型
resultType:输出结果类型
#{}:相当于占位符
#{id}:其中的id可以表示输入参数的名称,如果是简单类型名称可以任意
-->
<select id="findById" parameterType="int" resultType="com.meng.domain.User" >
select * from user where id=#{id}
</select>
<!--
根据用户名称来模糊查询用户信息列表;
${}:表示拼接sql语句
${value}:表示输入参数的名称,如果参数是简单类型,参数名称必须是value
-->
<select id="findByUsername" parameterType="java.lang.String"
resultType="com.meng.domain.User">
select * from user where username like '%${value}%'
</select>
c.利用MyBatis实现分页查询
-
三种方式
- 使用map方式
- 使用注解
- 使用序号
-
使用map
- map的key要和sql中的占位符保持名字一致
<select id="selectUserPage" resultType="com.mybatis.pojo.UserInfo">
select * from userinfo where sex=#{sex} limit #{startPage}, #{pagesize};
</select>
//测试
@Test
public void queryUserPage(){
Map<String,Integer> map=new HashMap<String, Integer>();
map.put("sex",2);
map.put("startPage",1);
map.put("pagesize",2);
List<UserInfo> users=ss.selectList("pojo.UserMapper.selectUserPage",map);
for (UserInfo user : users) {
System.out.println(user.toString());
}
}
- 使用注解
- 注意:mapper文件中的参数占位符的名字一定要和接口中参数的注解保持一致
<!--注解传参-->
<select id="selectByPage2" resultType="com.meng.domain.User">
SELECT * FROM user LIMIT #{offset}, #{pagesize}
</select>
接口
/**
* 根据分页参数查询
* @param offset 偏移量
* @param pagesize 每页条数
* @return 分页后的用户列表
*/
public List<User> selectByPage2(@Param(value="offset")int offset,@Param(value="pagesize")int pagesize);
测试
@Test
public void testSelectAuthorByPage2(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectByPage2(0,2);
for (User user : users) {
System.out.println(user.toString());
}
}
- 使用序号
- mapper文件中参数占位符的位置编号一定要和接口中参数的顺序保持一致
<!-- 分页:序号传参 MyBatis3.4.4版后不能直接使用#{0}要使用 #{arg0} -->
<select id="selectByPage3" resultType="com.qf.domain.User">
SELECT * FROM user LIMIT #{arg0}, #{arg1}
</select>
接口
/**
* 根据分页参数查询
* @param offset 偏移量
* @param pagesize 每页条数
* @return 分页后的用户列表
*/
public List<User> selectByPage3(int offset, int pagesize);
测试
@Test
public void testSelectAuthorByPage3(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectByPage3(0,2);
for (User user : users) {
System.out.println(user.toString());
}
}
d.返回Map类型查询结果
- Mybatis中查询结果集为Map的功能,只需要重写ResultHandler接口,然后用SqlSession 的select方法,将xml里面的映射文件的返回值配置成 HashMap 就可以了。具体过程如下
<select id="getAllUsers" resultType="java.util.Map">
SELECT * FROM user
</select>
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Map<String, Object>> allUsers = mapper.getAllUsers();
for (Map<String, Object> allUser : allUsers) {
Set<String> strings = allUser.keySet();
for (String key : strings) {
System.out.print("key="+key+"\t");
Object o = allUser.get(key);
System.out.println(o);
}
}
三.Mapper映射与Mapper接口注解
a.使用Mapper映射器
Mapper代理的开发方式,程序员只需要编写mapper接口(相当于dao接口)即可。Mybatis会自动的为mapper接口生成动态代理实现类。
-
开发要求
- mapper接口的全限定名要和mapper映射文件的namespace的值相同。
- mapper接口的方法名称要和mapper映射文件中的statement的id相同;
- mapper接口的方法参数只能有一个,且类型要和mapper映射文件中statement的parameterType的值保持一致
- mapper接口的返回值类型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致;
- 通过规范式的开发mapper接口,可以解决原始dao开发当中存在的问题:
- 模板代码已经去掉
- 剩下去不掉的操作数据库的代码,其实就是一行代码。这行代码中硬编码的部分,通过第一和第二个规范就可以解决
-
c.编写步骤
- 根据需求创建pojo类
- 编写全局配置文件(mybatis.xml)
- 根据需求编写映射文件(xxMapper.xml)
- 加载映射文件(加载xxMapper.xml)
- 编写mapper接口
- 编写测试代码
b.ResultMap映射定义
-
使用方法
- 如果查询出来的列名和属性名不一致,通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。
- 定义resultMap
- 使用resultMap作为statement的输出映射类型
- 如果查询出来的列名和属性名不一致,通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。
映射文件
<!-- 定义resultMap -->
<!--
[id]:定义resultMap的唯一标识
[type]:定义该resultMap最终映射的pojo对象
[id标签]:映射结果集的唯一标识列,如果是多个字段联合唯一,则定义多个id标签
[result标签]:映射结果集的普通列
[column]:SQL查询的列名,如果列有别名,则该处填写别名
[property]:pojo对象的属性名
-->
<resultMap type="user" id="userResultMap">
<id column="id_" property="id"/>
<result column="username_" property="username"/>
<result column="sex_" property="sex"/>
</resultMap>
<!-- 根据ID查询用户信息(resultMap)-->
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">
SELECT id id_,username username_,sex sex_ FROM USER WHERE id = #{id}
</select>
- 定义接口
//根据ID查询用户信息(resultMap)
public User findUserByIdResultMap(int id);
//定义Statement使用resultMap映射结果集时,Mapper接口定义方法的返回值类型为mapper映射文件中resultMap的type类型
- 测试代码
@Test
public void findUserByIdResultMapTest() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用mapper对象的方法
User user = userMapper.findUserByIdResultMap(1);
System.out.println(user);
// 关闭SqlSession
sqlSession.close();
}
c.Mapper接口注解
- @Insert、@Delete、@Update、@Select
- 增@Insert
- 在应用层手动指定主键
- 手动指定的方式不把主键区别看待,插入之前在应用层生成对象的时候就会给主键一个值,插入的时候与普通字段没啥区别
/**
* 插入记录,手动分配主键
*/
@Insert("INSERT INTO t_user (id, username, passwd) VALUES (#{id}, #{username}, #{passwd})")
int addUserAssignKey(User user);
表自增主键
自增主键对应着XML配置中的主键回填
/**
* 插入记录,数据库生成主键
* 使用Option来对应着XML设置的select标签的属性,userGeneratordKeys表示要使用自增主键,keyProperty用来指定主键字段的字段名。
* @param user
* @return
*/
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("INSERT INTO t_user (username, passwd) VALUES (#{username}, #{passwd})")
int addUserGeneratedKey(User user);
- 删除@Delete
@Delete("DELETE FROM t_user WHERE id=#{id}")
int delete(Long id);
- 改@Update
@Update("UPDATE t_user SET username=#{username}, passwd=#{passwd} WHERE id=#{id}")
int update(User user);
-
查@Select
- 在SQL语句中手动指定别名来匹配
- 在写SQL语句的时候,手动为每一个字段指定一个别名来跟对象的属性做匹配,适用于表字段名与对象属性名差异很大没有规律并且表字段不多的情况
@Select("SELECT id, username, passwd, birth_day AS birthDay FROM t_user WHERE id=#{id}") User loadByIdHandAlias(Long id);
- 使用ResultMap
- 对于表的字段名和对象的属性名没有太大相同点并且表中的字段挺多的情况下,应该使用ResultMap做适配
@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:
@ResultMap("userMap") @Select("SELECT * FROM t_user WHERE id=#{id}") User loadByIdResultMapReference(Long id);
使用@ResultMap来引用一个已经存在的ResultMap,这个ResultMap可以是在Java中使用@Results注解定义的,也可以是在XML中使用resultMap标签定义的
- 在SQL语句中手动指定别名来匹配