MyBatis
本人学疏才浅,如有错误请各位大牛批评指正!转载请注明出处!https://www.cnblogs.com/lee-yangyaozi/p/11040801.html
什么是MyBatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO 为数据库中的记录。
MyBatis的特点
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组建维护,提供XML标签,支持编写动态sql。
MyBatis的优缺点
1.sql语句与代码分离,存放于xml配置文件中:
优点:便于维护管理,不用在java代码中找这些语句;
缺点:JDBC方式可以用用打断点的方式调试,但是Mybatis不能,需要通过log4j日志输出日志信息帮助调试,然后在配置文件中修改。
2.用逻辑标签控制动态SQL的拼接:
优点:用标签代替编写逻辑代码;
缺点:拼接复杂SQL语句时,没有代码灵活,拼写比较复杂。不要使用变通的手段来应对这种复杂的语句。
3.查询的结果集与java对象自动映射:
优点:保证名称相同,配置好映射关系即可自动映射或者不配置映射关系,通过配置列名=字段名也可完成自动映射。
缺点:对开发人员所写的SQL依赖很强。
4.编写原生SQL:
优点:接近JDBC,比较灵活。
缺点:对SQL语句依赖程度很高;并且属于半自动,数据库移植比较麻烦,比如mysql数据库编程Oracle数据库,部分的sql语句需要调整。
使用MyBatis
1.导入jar包
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
其中log4j需要一个properties配置文件
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
2.编写主配置文件(一般命名SqlMapConfig.xml)
<?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>
<!-- 配置myabtis的默认数据源-->
<environments default="devlopment">
<!-- 数据源-->
<environment id="devlopment">
<!--事务管理 -->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/databaseName?characterEncoding=utf-8"></property>
<property name="username" value="username"></property>
<property name="password" value="password"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!--
使用资源的绝对路径
<mapper url=""/>
-->
<!-- 资源的相对路径-->
<mapper resource="User.xml"></mapper>
<!--
Mapper接口的全类名
要求:Mapper接口的名称与映射文件名称一致且必须在同一个目录下
<mapper class="com.qf.mapper.UserMapper"/>
-->
<!-- 加载某个包下的映射文件 (推荐)
要求:Mapper接口的名称与映射文件名称一致且必须在同一个目录下
<package name="com.qf.mapper"/>
-->
</mappers>
</configuration>
注:如果mapper 标签使用package标签,须在pom文件内加入以下代码
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
3.编写SQL.xml文件(命名建议与dao接口一致)
注:该文件有两种编写方式,主要区别在于mapper标签的namespace属性的值的设定,一种是自定义名字,一种是对应dao接口的全类名,分别对应调用时使用原生api调用和Mapper接口调用。
原生api方式
<?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="test">
<select id="selectById" parameterType="java.lang.Integer" resultType="com.qf.domain.User">
select * from user where id = #{id}
</insert>
</mapper>
Mapper接口方式
<?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="dao接口全类名">
<select id="selectById" parameterType="java.lang.Integer" resultType="com.qf.domain.User">
select * from user where id = #{id}
</insert>
</mapper>
注:id必须和接口定义的方法名一致!
4.调用SQL.xml
调用sql对应也有两种方式,两种调用方式都需要首先加载主配置文件并创建一个SQLSession对象。
//加载mybatis配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//使用sqlSessionFactoryBuild来创建一个sqlSessionFactory
SqlSessionFactory sqFactroy = new SqlSessionFactoryBuilder().build(resourceAsStream);
//获取到sqlsession 进行调取api
SqlSession sqlSession = sqFactroy.openSession();
原生api方式
//通过id来进行查询
User user = sqlSession.selectOne("test.selectById", 37);
System.out.println(user);
注:通过namespace.id定位要执行的sql语句。
Mapper接口方式
//调取对应的接口 UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user1 = mapper.selectById2(38); System.out.println(user1);
注:首先通过反射获取对应接口,然后调用接口方法。由于接口方法名和sql.xml文件内sql语句id一致,所以可以映射到对应的sql语句上。
注:在实际开发过程中,Mapper接口方式使用的多一些。
MyBatis进阶使用
1.多参数应用
有时候一个sql语句需要同时传多个基本类型参数,这时需使用多参数传递技术,共有三种方式。
select * from user limit #{offset}, #{pagesize}
使用Map传递
接口:
List<User> selectUserByPage(Map<String, Object> paramList);
调用:
@Test
public void testSelectUserByPage(){
Map<String, Object> map = new HashMap<String, Object>();
map.put("offset", 0);
map.put("pagesize", 2);
UserMapper userDao = session.getMapper(UserMapper.class);
List<User> userList = userDao.selectUserByPage(map);
for (int i = 0; i < userList.size(); i++) {
System.out.println(userList.get(i));
}
}
注:map的key必须和sql语句占位符名字一致!
使用注解方式
接口:
List<User> selectUserByPage(@Param(value="offset")int offset, @Param(value="pagesize")int pagesize);
调用:
@Test
public void testSelectUserByPage(){
UserMapper userDao = session.getMapper(UserMapper.class);
List<User> userList = userDao.selectUserByPage2(0, 2);
for (int i = 0; i < userList.size(); i++) {
System.out.println(userList.get(i));
}
}
注:注解的参数名必须和sql语句的占位符名字一致!
使用序号方式
sql语句:
select * from author limit #{0}, #{1}
接口:
List<User> selectUserByPage(int offset, int pagesize);
调用:
@Test
public void testSelectUserByPage(){
UserMapper userDao = session.getMapper(UserMapper.class);
List<User> userList = userDao.selectUserByPage3(1, 1);
for (int i = 0; i < userList.size(); i++) {
System.out.println(userList.get(i));
}
}
注:接口中参数的位置顺序必须和sql语句中的占位符编号一致!
2.延迟加载
开发中表之间的关联十分常见,在查询数据时经常需要查询多张表的集合数据,但这些所有表的数据却并不是同时需要展示给用户的。
如用户表同时关联了个人信息以及订单,查询时正常是将用户表、信息表和订单表内同一用户所有信息查询成一个集合,但此时的功能场景是只查询订单不查询个人信息,如果全部查询出来是属于浪费资源的行为。延迟加载就是这类问题的解决方案,开启延迟加载后,程序首先只查询用户表,当程序需要个人信息或订单时再去进行查询和加载,节省系统开销。
开启延迟加载
MyBatis默认未开启延迟加载,须手动开启。在主配置文件内添加如下代码:
<settings>
<!-- 启用延迟加载特性,不配置默认关闭该特性-->
<setting name="lazyLoadingEnabled" value="true"></setting>
<!-- 按需加载: false:使用关联属性,及时加载;true,加载对象,则加载所有属性-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
注:个人认为aggressiveLazyLoading设置为false才是真的延迟加载,因为设为true还是会查询多表(执行多个sql语句),设为false是需要从表数据时才执行sql语句。
编写实体类
从表
public class UserLogin {
private int id;
private String userName;
private String password;
}
主表
public class UserDetail {
private int id;
private String nickName;
private String sex;
private String address;
private Date birthday;
private int userId;
private UserLogin userLogin; // 从表对象
private List<Orders> ordersList; // 从表对象
}
编写sql.xml
主表
<resultMap id="resultBase" type="com.qf.domain.UserDetail">
<id column="id" property="id"></id>
<result column="nick_name" property="nickName"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<result column="user_id" property="userId"></result>
<association property="userLogin" select="com.qf.dao.UserLoginMapper.findById" column="user_id"></association>
</resultMap>
<select id="findAll" resultMap="resultBase">
select * from user_detail
</select>
从表
<select id="findById" resultMap="resultBase" parameterType="int">
select * from user_login where id=#{id}
</select>
注:user_id是主表与从表的关联字段,对应从表的id字段。
调用:
@Test
public void lazyLoadTest(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDetailMapper mapper = sqlSession.getMapper(UserDetailMapper.class);
List<UserDetail> userDetails= mapper.findAll();
for (UserDetail userDetail:userDetails){
System.out.println(userDetail.getId());
}
sqlSession.close();
}
注:调用处若aggressiveLazyLoading设为true,即使不打印UserLogin的数据也会执行查询UserLogin的SQL语句;若设为false,则会在打印(调用)UserLogin的数据时才会执行查询UserLogin的SQL语句。
3.嵌套映射
MyBatis的resultMap标签是一个非常强大的标签,当实体类属性名和数据库字段名不一致时,可以使用resultMap进行映射;实际项目中数据库中的表一般都不会单独存在,一般都会存在一对一或一对多的关联关系,使用resultMap标签可以使实体类互相嵌套映射关系。MyBatis中有三种嵌套映射关系:嵌套查询、嵌套结果、嵌套集合。
嵌套查询
嵌套查询类似延迟加载,sql.xml写法几乎一样,即在一个查询语句里嵌套另一个对象的查询语句,只不过延迟加载会根据对象数据是调用时机延迟执行查询语句,而嵌套查询是一口气把嵌套的所有sql与执行完毕。
sql.xml文件
<resultMap id="resultObj" type="com.qf.domain.UserDetail">
<id column="id" property="id"></id>
<result column="nick_name" property="nickName"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<result column="user_id" property="userId"></result>
<association property="userLogin" select="com.qf.dao.UserLoginMapper.findById" column="user_id">
<id column="id" property="id"></id>
<result column="username" property="userName"></result>
<result column="password" property="password"></result>
</association>
</resultMap>
<select id="findById" resultMap="resultObj">
select * from user_detail where id = #{id}
</select>
接口:
UserDetail findById(int id);
嵌套结果
嵌套结果与嵌套查询不同的是,嵌套结果是执行一条SQL语句查询多张表,即表连接查询,返回结果是多张表数据的集合;嵌套查询是执行多条SQL语句分别查询对应的表,返回结果同样在一个实体类对象里。
SQL.xml文件
<resultMap id="resultObj" type="com.qf.domain.UserDetail">
<id column="id" property="id"></id>
<result column="nick_name" property="nickName"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<result column="user_id" property="userId"></result>
<association property="userLogin" resultMap="userLogin"></association>
</resultMap>
<resultMap id="userLogin" type="userLogin">
<id column="id" property="id"></id>
<result column="username" property="userName"></result>
<result column="password" property="password"></result>
</resultMap>
<select id="findById" resultMap="resultObj">
select * from user_detail a,user_login b where a.user_id = b.id and a.id= #{id}
</select>
接口:
UserDetail findById(int id);
嵌套集合
嵌套集合的应用场景是一对多的表关联结构,如一位用户有多个订单记录,此时查询的结果就需要返回一个用户和一个订单集合。
SQL.xml文件
<resultMap id="resultList" type="com.qf.domain.UserDetail">
<id column="id" property="id"></id>
<result column="nick_name" property="nickName"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<result column="user_id" property="userId"></result>
<collection property="ordersList" ofType="com.qf.domain.Orders" javaType="java.util.ArrayList">
<id property="ordersid" column="ordersid" />
<result property="orederUserId" column="orederUserId" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
</collection>
</resultMap>
<select id="findById" resultMap="resultList">
select a.address,a.nick_name,a.id,a.sex,a.birthday,a.user_id,b.id as ordersid,b.createtime,b.number,b.user_id as orederUserId from user_detail a,orders b where a.user_id = b.user_id and a.id= #{id}
</select>
注:property用于指定在Java实体类是保存集合关系的属性名称;javaType用于指定在Java实体类中使用什么类型来保存集合数据,多数情况下这个属性可以省略的;ofType用于指定集合中包含的类型。
接口:
UserDetail findById(int id);
4.动态SQL
MyBatis的动态sql可以改善在提交数据时,如果只更新部分字段,导致其他字段被设置为null的情况,因为MyBatis提交引用类型是以对象形式提交的,若只更新部分字段,该对象没有set数据的属性默认为null,一旦执行update方法,会导致其余字段变成null的情况;还可以解决查询时不确定查询条件的,如图书管理系统,用户可能根据书名、出版社、作者其中之一或全部条件进行查询,如果不使用动态SQL,一般解决办法是判断参数是否为空,调用对应的SQL语句,这样的话同一功能需要编写多个SQL语句,极大降低开发效率。MyBatis的动态SQL主要有:if、choose(when、otherwize)、trim、where、set、foreach。
if元素
示例:解决不确定查询条件的查询功能
<select id="dynamicIfTest" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</select>
示例:解决更新数据其他字段为null
<update id="dynamicIfTest" parameterType="Blog" resultType="Blog">
update user set
<if test="username != null">
user_name = #{username}
</if>
<if test="password != null">
and pass_word = #{password}
</if>
</update>
choose元素
choose-when-otherwize元素类似于java的switch-case-default语句。
<select id="dynamicChooseTest" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="content != null">
and content = #{content}
</when>
<otherwise>
and owner = "owner1"
</otherwise>
</choose>
</select>
where元素
where元素就是代替了SQL语句中的where关键字,在需要使用where关键词的地方使用where元素,不用担心元素内是否会有错误代码,where元素会自动的剔除多于的and、or和增加空格。
<select id="dynamicWhereTest" parameterType="Blog" resultType="Blog">
select * from t_blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</where>
</select>
set元素
set元素功能和where元素类似,替换SQL语句中的set关键字,并且自动的格式化SQL语句,使其能够正常执行。
<update id="dynamicSetTest" parameterType="Blog">
update t_blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="content != null">
content = #{content},
</if>
<if test="owner != null">
owner = #{owner}
</if>
</set>
where id = #{id}
</update>
trim元素
trim是更灵活用来去处多余关键字的标签,它可以用来实现 where 和 set 的效果。
示例:trim代替where元素
<select id="getusertlist_if_trim" resultmap="resultmap_user">
select * from user u
<trim prefix="where" prefixoverrides="and|or">
<if test="username !=null ">
u.username like concat(concat('%', #{username, jdbctype=varchar}),'%')
</if>
<if test="sex != null and sex != '' ">
and u.sex = #{sex, jdbctype=integer}
</if>
<if test="birthday != null ">
and u.birthday = #{birthday, jdbctype=date}
</if>
</trim>
</select>
示例:trim代替set元素
<update id="updateuser_if_trim" parametertype="com.qf.pojo.user">
update user
<trim prefix="set" suffixoverrides=",">
<if test="username != null and username != '' ">
username = #{username},
</if>
<if test="sex != null and sex != '' ">
sex = #{sex},
</if>
<if test="birthday != null ">
birthday = #{birthday},
</if>
</trim>
where user_id = #{user_id}
</update>
注:trim可以更灵活的控制SQL语句的结构,但个人认为没啥实际意义。
forEach元素
forEach元素主要应用在SQL语句中使用in关键字时,它可以在SQL语句中进行迭代一个集合,其主要属性有:
- item表示集合中每一个元素进行迭代时的别名
- index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置
- open表示该语句以什么开始
- separator表示在每次进行迭代之间以什么符号作为分隔 符
- close表示以什么结束
- collection表示传入的参数类型,list对应集合,array对应数组,map对应Map
集合类型
SQL.xml文件
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
调用
@Test
public void dynamicForeachTest() {
SqlSession session = Util.getSqlSessionFactory().openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
List ids = new ArrayList();
ids.add(1);
ids.add(3);
ids.add(6);
List blogs = blogMapper.dynamicForeachTest(ids);
for (Blog blog : blogs){
System.out.println(blog);
}
}
数组类型
SQL.xml文件
<select id="dynamicForeach2Test" resultType="Blog">
select * from t_blog where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
调用
@Test
public void dynamicForeach2Test() {
SqlSession session = Util.getSqlSessionFactory().openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
int[] ids = new int[] {1,3,6,9};
List blogs = blogMapper.dynamicForeach2Test(ids);
for (Blog blog : blogs){}
System.out.println(blog);
}
}
Map类型
SQL.xml文件
<select id="dynamicForeach3Test" resultType="Blog">
select * from t_blog where title like "%"#{title}"%" and id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
调用
@Test
public void dynamicForeach3Test() {
SqlSession session = Util.getSqlSessionFactory().openSession();
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
List ids = new ArrayList();
ids.add(1);
ids.add(2);
ids.add(3);
Map params = new HashMap();
params.put("ids", ids);
params.put("title", "中国");
List blogs = blogMapper.dynamicForeach3Test(params);
for (Blog blog : blogs){
System.out.println(blog);
}
}
5.MyBatis使用注解模式
MyBatis对应成增删改查有如下注解:
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
但是注解对于复杂的SQL语句心有余而力不足,使用注解实现简单的增删改查效率还是挺高的。