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语句心有余而力不足,使用注解实现简单的增删改查效率还是挺高的。

posted @ 2019-06-18 21:30  李暘  阅读(797)  评论(0编辑  收藏  举报