MyBatis框架教程「实践与工具类封装」

实践与工具类封装

上一篇文章我们学习了MyBatis框架的环境搭建以及对sqlsessionfactory有个大致的了解,这篇文章就要运用搭建好的环境进行增删改查并且指出一些细节。

同样我们会进行mybatis工具类的封装,把共同的生成sqlSession的代码进行提取封装成工具类,需要sqlSession直接去工具类拿就可以了。文章中我们还可以学习到一些关于日志处理的知识,初步对单元测试的使用。

我们的案例使用的简单的Java项目不是基于动态web项目,因为我们现在处于学习阶段,关于SSM框架的整合会在以后的文章中进行详细的讲解。

1.案例截图

2. User.java

public class User {
 private Integer user_id;
 private String user_name;
 private String account;
 private String password;
 private Integer status;   
}

注意:由于篇幅限制,我们对User属性的getter,setter和tostring方法进行省略

3. mybatis-config.xml

<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTDConfig 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
  <configuration>
    <!-- 配置环境 -->
   <environments default="jujidi">
    <environment id="jujidi">
     <transactionManager type="JDBC"></transactionManager>
       <dataSource type="POOLED">
         <property name="driver" value="com.mysql.jdbc.Driver"/>
         <property name="url" value="jdbc:mysql://localhost:3306/dbname"/>
         <property name="username" value="root"/>
         <property name="password" value="root"/>
       </dataSource>
     </environment>
  </environments>
  <!-- 加载映射文件 -->
  <mappers>
    <mapper resource="com/jujidi/test/TestMapper.xml" />
  </mappers>
</configuration>

这是Mybatis框架的核心配置文件,其中配置了相关环境和加载映射文件,环境中确定数据源,在数据源中配置你需要链接数据库的相关信息,比如用户名、密码。映射文件就是你编写SQL的xml,通过此核心配置文件进行加载关联。

注意这里的关键点:

  • 默认的环境 ID(default="development")
  • 每个 environment 元素定义的环境 ID
  • 事务管理器的配置(type="JDBC")
  • 数据源的配置(type="POOLED")

可以对环境随意命名,但一定要保证默认的环境 ID 要只匹配其中一个环境 ID

4. TestMapper.xml

<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTDMapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.jujidi.model.User">
 <!-- 通过id返回数据 -->
 <select id="load" parameterType="integer" resultType="com.jujidi.test.User">
    select * from user where user_id=#{user_id}
 </select>
 <select id="userList" resultType="map">
   select * from user
 </select>
 <select id="count" resultType="integer">
    select count(*) from user
 </select>
 <insert useGeneratedKeys="true" keyProperty="user_id" id="add" parameterType="com.jujidi.test.User" >
    insert into user(user_name,account,password) values (#{user_name},#{account},#{password})
 </insert>
</mapper>

Mapper是映射文件,对应多个标签(可以理解为SQL),每一个标签有唯一的id用来定位。

上方的代码是此文章案例使用的配置,下面我们扩展MyBatis进行简单增删改查的mapper文件写法:

5. 映射文件写法扩展

插入:

<mapper namespace="com.jujidi.model.User">
    <insert id="insertPerson" parameterType="com.jujidi.model.User">
      INSERT INTO user(user_name,password) VALUES(#{name},#{password})
    </insert>
  </mapper>
 
  <mapper namespace="需要实现接口的全限定类名">
   <insert id="需要实现的接口里的方法名" parameterType="方法参数类型,如果是对象要写全限定类名">
    INSERT sql命令(命令里通过#{}获取对象属性)
  </insert>
  <mapper>

查询:

<!-- 通过id返回数据 -->
<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
  select * from user where user_id=#{user_id}
</select>

<select id="方法名"  parameterType="方法参数类型"  resultType="方法返回值类型,全类名">
  SELECT 表里字段名 AS 结果字段名 FROM 表名 WHERE 条件
  <!--注意:结果字段名与属性名保持一致,区分大小写-->
</select>

修改:

<update id="updateUserById" parameterType="com.jujidi.test.User">  
   update user   
         set name=#{name},status=#{status}  
      where user_id=#{user_id}  
 </update>

删除:

<delete id="deleteUserById"parameterType="int">  
   delete from user  
    where user_id=#{id}  
 </delete>

关于传递参数:

<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
     select * from user where user_id=#{user_id}
</select>

通过上面几行代码描述:在mybatis映射文件的配置中,有select等标签都提到了parameterType属性,parameterType为输入参数,在配置的时候,配置相应的输入参数类型即可。

parameterType有基本数据类型和复杂的数据类型配置:

1.基本数据类型,如输入参数只有一个,其数据类型可以是基本的数据类型

2.复杂数据类型:包含java实体类,map

6. 映射文件配置详解

<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
   select * from user where user_id=#{user_id}
</select>
<select id="userList" resultType="map">
   select * from user
</select>

第一个select标签:此标签id为load,传入integer类型的user_id,去user表中查询此id的用户信息,把结果返回。返回值的类型为User对象。

第二个select标签:此标签id为userList,不需要参数,去user表中查询所有用户信息,进行结果反回。返回值的类型为map。

上方的两条select语句中,有一个属性是:resultType是SQL映射文件中定义返回值类型,返回值有基本类型,对象类型,List类型,Map类型等。

  • 基本类型:resultType=基本类型
  • List类型:resultType=List中元素的类型
  • Map类型
    • 单条记录:resultType=map
    • 多条记录:resultType =Map中value的类型

代码中第一条select语句的resultType使用的对象类型,resultType直接写的对象User的全类名,而第二条select语句返回类型使用的是map,显然效果都是一样的。

7. 为什么是使用map而不是Map呢?

刚刚配置中的resultType="map",为什么是map,不是Map呢?

如果想写Map,需要写成全限定类名,即:resultType = "java.util.Map" map是java.util.Map的一个简写,还有其他的简写可以参考下方表格:

我们看下方两个语句的resultType:

<select id="load" parameterType="integer" resultType="com.jujidi.test.User">
   select * from user where user_id=#{user_id}
</select>
<select id="userList" resultType="map">
   select * from user
</select>

对于resultType属性,填写User的全类名和map类型效果都一样,还是有一些区别的。比如:如果使用User的全类名作为resultType的参数的话,我们需要保证实体类的属性名字和数据库字段的名字相同,否则的话是接不到值的。

想要实体类的属性和数据库字段不相同,我们有办法解决,可以通过更改SQL语句(别名)等方式来达到目的。

除了resultType属性外还有一个叫做:resultMap,它们俩的区别在后面的文章进行讲解。

8. MyBatisTest.java

public class MybatisTest {
public static void main(String[] args ) {
  SqlSession sqlSession = null;
  try{
      //加载核心配置文件
      InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
      //创建sqlsession工厂 -->相当于connection
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
      //获取sqlsession -->相当于执行sql语句对象
      sqlSession = sqlSessionFactory.openSession();
      //执行sql
     User user = sqlSession.selectOne("com.jujidi.model.User.load",1);
      System.out.println(user);
      List<Map<String,Object>> userList = sqlSession.selectList("com.jujidi.model.User.userList");
      System.out.println(userList);
      Integer count =sqlSession.selectOne("com.jujidi.model.User.count");
      System.out.println(count);
      }catch( Exception e){
     // TODO Auto-generated catch block
     e.printStackTrace();
      }finally{ 
         if(sqlSession!=null){
      sqlSession.close();
     }
   }
 }
}

上方是测试类,测试类中我们加载核心配置文件、利用核心配置文件的信息来获取sqlsession工厂,工厂生成sqlsession。然后定位映射文件Mapper中的SQL语句进行执行相应查询方法。

9. 事务的提交

测试类中执行下方的insert方法:

public class MybatisTest {
 public static void main(String[] args ) {
  SqlSession sqlSession = null;
  try{
       //加载核心配置文件
      InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
      //创建sqlsession工厂 -->相当于connection
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
      //获取sqlsession -->相当于执行sql语句对象
      sqlSession = sqlSessionFactory.openSession();
     User user = new User();
     user.setAccount("admin");
     user.setPassword("123");
     user.setUser_name("王久一");
     sqlSession.insert("com.jujidi.model.User.add",user);
     }catch( Exception e){
      // TODO Auto-generated catch block
      e.printStackTrace();
     }finally{ 
         if(sqlSession!=null){
       sqlSession.close();
      }
   }
  }
}

Mapper.xml中语句为:

<insert useGeneratedKeys="true" keyProperty="user_id" id="add" parameterType="com.jujidi.test.User" >
   insert into user(user_name,account,password) values (#{user_name},#{account},#{password})
</insert>

当我们执行insert方法时候,我们会发现:运行期间没有报错,但是数据库中并没有多出刚刚插入的记录。

这是因为进行了插入事务,事务并没有提交。不像以前学习的JDBC会自动提交事务。当我们利用MyBatis执行插入语句后,好像我们使用Navicat工具修改了表中的数据没有点保存一样。必须手动进行提交事务,我们对代码做出如下修改:

MybatisTest .java

public class MybatisTest {
 public static void main(String[] args ) {
   SqlSession sqlSession = null;
   try{
       //加载核心配置文件
     InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
     //创建sqlsession工厂 -->相当于connection
     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
     //获取sqlsession -->相当于执行sql语句对象
     sqlSession = sqlSessionFactory.openSession();
     User user = new User();
     user.setAccount("admin");
     user.setPassword("123");
     user.setUser_name("王久一");
     sqlSession.insert("com.jujidi.model.User.add",user);
    sqlSession.commit();
     System.out.println(user.getUser_id());
     }catch( Exception e){
      // TODO Auto-generated catch block
      e.printStackTrace();
     }finally{
        if(sqlSession!=null){
       sqlSession.close();
      }
    }
  }
}

加上了sqlSession.commit()语句,这时候就可以成功的修改表中的数据。还有一个比较神奇的小细节:在我们还没有添加sqlSession.commit()语句时,虽然运行后没办法真正插入一条数据,但是想要插入数据的ID还是会生成的。

10. 获取ID

当我们插入了一条数据,怎么来获得新插入的数据的id?

再回到这条mapper语句:

<insert useGeneratedKeys="true" keyProperty="user_id"  id="add"  parameterType="com.jujidi.test.User" >
   insert into user(user_name,account,password) values (#{user_name},#{account},#{password})
</insert>

上方的insert标签属性添加了:

useGeneratedKeys="true" keyProperty="user_id"

这两个属性后,执行下方的代码:

sqlSession.insert("com.jujidi.model.User.add",user);
 sqlSession.commit();
 System.out.println(user.getUser_id());

提交插入一条记录后,可以通过user.getUser_id()来获取刚插入记录的ID.

11. MyBatis工具类的封装

我们发现当使用Mybatis进行操作时,加载核心配置文件、创建sqlsession工厂等都是重复的代码,于是我们想着自己封装一个Mybatis工具类,此工具类负责返回一个sqlsession和一些关闭会话的操作。

工具类:MybatisUtil.java

public final class MybatisUtil {
 private MybatisUtil(){}
 private static final String PATH = "mybatis-config.xml";
 private static InputStream is = null;
 private static SqlSessionFactory sqlSessionFactory = null;
 static{
   try{
      //加载核心配置文件
       is = Resources.getResourceAsStream(PATH);
       //创建sqlsession工厂 -->相当于connection
       sqlSessionFactory = newSqlSessionFactoryBuilder().build(is);
       }catch(Exception e){
      e.printStackTrace();
      throw new RuntimeException("加载映射文件失败可能是你的映射文件写错了原因:"+e.getMessage());
      }
    }
    //获取sqlsession -->相当于执行sql语句对象
    public static SqlSession getSqlSession(){
     return sqlSessionFactory.openSession();
    }
    public static void closeSqlSession(SqlSession sqlSession){
     if(sqlSession!=null){
       sqlSession.close();
    }
  }
}

对于上方配置文件的关键字做出一些解释:

11.1 不可继承

final:不可以被继承

final:修饰一个变量path,变成常量不可以修改其值

11.2 不可实例化

private MybatisUtil(){}

工具类类名是:MybatisUtil。构造方法是用public修饰,但是上方使用了private。我们知道构造方法是用来实例化对象的,private来修饰说明本类不能被实例化。

11.3 初始化

static{
   try{
     ...
    }catch(IOException e){
      ...
    }
   }

上方代码:类加载就会加载静态代码块的代码 ,类初始化的时候就加载配置文件获取工厂。

11.4. 异常处理

catch(IOException e){
  e.printStackTrace();
  throw new RuntimeException("加载映射文件失败可能是你的映射文件写错了原因:"+e.getMessage());
}

对于catch到的异常 "加载映射文件失败,可能是映射文件写错了"要解释一下:

1. mybatis-config 配置了映射文件,映射文件配置出错,加载核心配置文件的时候也会报错

2. 为什么不提示mybatis-config.xml写错了呢?因为相应的环境参数配置一次就完毕,就是有错误调试成功以后很少会改动xml,而映射文件是常常需要我们编写修改的,所以抛出这样的提示更利于报错的时候准确找到错误。

12.单元测试

我们知道main()方法只能有一个,如果都在main方法里面进行测试代码,就需要注释掉测试完毕的方法,仅仅运行自己当前需要测试的功能方法,这样显得杂乱无章,所以我们学习一下单元测试,不仅对以后的学习有帮助,在工作中也是经常用到。

12.1 新建java工程

添加JUnit相关Jar包

右键项目:Build Path->Configure Builde Path->Add Library->JUnit->Next->Finish

Test01 .java

public class Test01 {
   @Test
  public void test01(){
    System.out.println("王久一");
  }

  @Test
  public void test02(){
    System.out.println("小小詹");
  }

  @Test
  public void test03(){
    System.out.println("老邓头");
  }
}

12.2 测试运行

对于上方代码:我们测试test01()方法,只需要选中test01:

右键->Run as->JUnit Test:

控制台便会打印出:王久一

以此类推,我们分别运行test02和test03方法,也会有相同的效果。

接下来修改代码:

public class Test01 {
  @Before
  public void before(){
    System.out.println("Web项目聚集地before");
  }

  @After
  public void after(){
   System.out.println("Web项目聚集地after");
  }

   @Test
  public void test01(){
    System.out.println("王久一");
  }

  @Test
  public void test02(){
    System.out.println("小小詹");
  }

  @Test
  public void test03(){
    System.out.println("老邓头");
  }
}

12.3 再次运行

测试test01()方法,控制台打印为:

添加@Before@After注解后,产生的效果可想而知。

12.4 一起运行

如果不单个运行,一起运行是什么效果呢?

13. log4j日志包

最后方便开发调试,我们需要增加日志功能:

导入log4j包

在classpath下添加log4j.properties文件:

# Global loggingconfiguration
log4j.rootLogger=ERROR,stdout
# 注意log4j.logger后面是需要日志处理mapper的namespace名称
log4j.logger.com.jujidi.model.User=TRACE
# 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

通过第四行的配置和映射文件关联,在执行相关联mapper中的SQL就会在控制台打印出日志信息。

重要说明:实际开发中是不会使用Mybatis工具类来进行开发的,而是SSM框架整合后,通过接口代理的方式来实现对数据库的操纵。

为了使初学者对Mybatis框架有一个独立且系统的认识,所以采用这种系统的教学方式,而不是一开始就是整合。我们会在学习过程中逐渐拓展,最终达到实际开发所需要的本领。

原文发布于微信公众号 - Web项目聚集地(web_resource)

原文发表时间:2018-08-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JavaEdge

MyBatis实战(二)-一级缓存原理解析1 概论2 一级缓存是怎样组织的3 一级缓存的生命周期4 一级缓存的工作流程5 Cache接口的设计

每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话

10330
来自专栏Danny的专栏

【MyBatis框架点滴】——MyBatis一对一查询

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

10620
来自专栏分布式系统进阶

Kafka集群Metadata管理Kafka源码分析-汇总

可以看到是调用了ReplicaManager.maybeUpdateMetadataCache方法, 里面又会调用到MetadataCache.updateCa...

28520
来自专栏我有一个梦想

vc++ 在程序中运行另一个程序的方法

在vc++ 程序中运行另一个程序的方法有三个: WinExec(),ShellExcute()和CreateProcess() 三个SDK函数: WinExec...

48890
来自专栏Java后端技术栈

SqlSessionTemplate是如何保证MyBatis中SqlSession的线程安全的?

周六的时候发了一篇面试题的整理《2018年最全Java面试通关秘籍汇总集!》,又在底部留言中补充了一道关于SqlSession线程安全性的问题,今天就带大家初步...

22030
来自专栏Danny的专栏

【MyBatis框架点滴】——MyBatis开发DAO的两种方法:原始DAO开发方法和Mapper代理方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

9030
来自专栏王清培的专栏

log4net 自定义Layout日志字段

最近在使用log4net的时候有一个简单的需求,就是自定义个格式化输出符。这个输出符是专门用来帮我记录下业务ID、业务类型的。比如,“businessID:32...

9200
来自专栏cnblogs

knockoutjs 上自己实现的flux

在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合问题。 一、其实简单 flux的设计理念和实现方案,很大程度上人借...

23680
来自专栏fixzd

redis系列:通过队列案例学习list命令

这一篇文章将讲述Redis中的list类型命令,同样也是通过demo来讲述,其他部分这里就不在赘述了。

10920
来自专栏公众号_薛勤的博客

深入理解[Master-Worker模式]原理与技术

Master-Worker模式是常用的并行模式之一。它的核心思想是,系统由两类进程协作工作:Master进程和Worker进程。Master进程负责接收和分配任...

25150

扫码关注云+社区

领取腾讯云代金券