SSM框架开发web项目系列(二) MyBatis真正的力量

  前言

  上篇SSM框架环境搭建篇,演示了我们进行web开发必不可少的一些配置和准备工作,如果这方面还有疑问的地方,可以先参考上一篇“SSM框架开发web项目系列(一) 环境搭建篇”。本文主要介绍MyBatis的基础内容,包括基本概念、开发步骤、使用实例等。说起MyBatis,工作中做过SSH/SSM相关Web开发的或者正在学习MyBatis的人或多或少都会接触到类似“MyBatisHibernate有什么区别?”,“MyBatisHibernate哪个更好?”,“为什么Mybatis用的人越来越多?”等等...记得面试问题,区别问的最多,有次被面试官问到更喜欢用哪一个?明明已经知道这个公司介绍用的是SSM了,我答了个Hibernate,并说先用的也是Hibernate,或许初恋的感觉过于深刻吧...谁好谁差这种主观性问题,我们不争论,但是不容质疑的是两者都为企业级开发做出巨大贡献。同时,带着问题和求知欲去学习往往会让学习效率大大提高,因为许多问题在困惑你的同时,也为你指引了方向。就正如你得会用它,理解它了,才知道它在某方面为什么会不足。

  MyBatis介绍

  早些时候,Apache有一个开源项目iBatis,后来改名了叫Mybatis,所以我们在网上有时候会看到一些早期的文章有时候看到iBatis,其实是同一个东西。同时,大家可以看下MyBatis源码工程结构,如下图,也能发现这个问题。

 

  MyBatis是一个半自动-ORM-持久层框架,下面分别介绍这三个概念,如果了解的的可以直接跳过以节省时间。

  首先持久层比较好理解,就是针对数据库的各种操作持久化层面,我们用jdbc也可以对数据库中表进行增删可查;WEB开发分层结构中,类似的还有业务层、控制层,MyBatis所做用的层,主要是用于与数据库打交道。

  其次ORMObject Relational Mapping,对象关系映射)是一种技术,也是思想,如果用过jdbc的原生方式操作数据的应该都知道,其中各种获取和更新的操作都已有相关的API了,但是这个过程实在太繁琐,获取连接、构造语句、发送SQL和接收数据、最后是处理数据和关闭流等等...实际的工作开发中,显然不太适用。也许是有人想到,最麻烦的地方在于获取数据后的处理过程,为了简化这一过程,以Java中面向对象的思维构造了这一个ORM关系模型,即每张数据表对应一个Java类,数据表中每一条记录分别对应Java类的一个实体对象,数据表中每个字段对应Java类中的一个属性,这样一来Java中一切皆对象,数据库里的东西既然已经对应映射到Java概念中来,我们再用面向对象的思维去操作数据库,就大大简化了开发流程。

  最后,解释半自动,既然有半自动,应该就有全自动,例如Hibernate就是一个全自动的ORM持久层框架,它在建立数据库和bean对象关系映射模型的同时,提供的api还会帮助我们自动生成和发送SQL语句去操作数据库,而MyBatis略有不同,它也建立了对象关系映射模型,但是并不会帮助我们生成SQL语句,需要我们自己写SQL语句,不少人可能会觉得别人都可以帮你自动生成了这还要自己写,不是没事找事吗?但是恰恰相反,很多人因为这点喜欢上了MyBatis,灵活且透明,自己动手不解释。

  MyBatis重要对象

  关于MyBatis的学习使用过程中,依次要注意的四个对象有SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper实例。

  1.SqlSessionFactoryBuilder

  SqlSessionFactoryBuilder是一个类,里面定义许多重载的build方法,通过build方法可以获取SqlSessionFactory对象,即SqlSession工厂实例

  2.SqlSessionFactory

  SqlSessionFactory是一个接口,看名字应该能明白是SqlSession工厂,里面定义了许多重载的openSession方法,用于获取SqlSession对象

  3.SqlSession

  SqlSession是一个很关键的接口,通过它我们可以执行发送SQL语句、获得Mapper实例等等。以它的第一个方法为例, <T> T selectOne(String statement); 方法名很容易理解,获取数据表的一条记录(在Java中对应返回一个实体类对象),前面的泛型对应的就是实体类的类型,关于String类型的参数statement,用过原生的JDBC操作数据库的应该不会陌生,在使用JDBC过程中,有个Statement对象,通过该对象的类似 ResultSet executeQuery(String sql) throws SQLException; 等方法可以发送SQL语句,方法里的字符串类型参数 sql 就是我们要发送的sql语句,而前面的String statement 同样也是代表我们的sql语句总的来说,selectOne中的statement代表sql语句,JDBC中的statement是能发送sql语句的对象实例,不可混淆。

  4.Mapper实例

  Mapper实例就是我们在dao层定义的定义的接口实例,我们在service层中注入dao对象时关联的是该接口名,而实际上我们拿到了该接口实例也就是Mapper实例。而我们在web开发过程中,持久层的相关方法都定义在Mapper接口中,所以四个对象里我们在前面环境搭建篇比较容易发现的也就是这个Mapper实例所属接口,即PersonMappr接口。Mapper实例可以通过SqlSession的getMapper方法获得。

  MyBatis配置文件

  

  以上为MyBatis配置文件下节点的结构分布图,熟悉DTD的根据org/apache/ibatis/builder/xml下的约束文件mybatis-*-config.dtd也可以获取XML规范。

  environment(环境)

  在开发中,我们要连接到数据库,往往都需要配置一个数据源,其中包括数据库url参数,用户名和密码等等,另外还有事事务管理器配置。MyBatis中也不例外,在这里针对一个数据库的连接,有一个environment(环境)与之对应,如果有多个数据库,我们可以在environments下定义多个environment。environment下面通过dataSource和transactionManager的property属性进行数据源和事务管理器配置。

  typeAliases(类型命名)

  这个是开发中很实用的配置,前面配置篇中mybatis-config.xml中的<typeAlias alias="person" type="com.mmm.pojo.Person" />,给类绑定别名,然后在其他地方引用时可以不用写类的全名(类似com.xxx.Xxx形式),直接写别名即可,例如后面personMapper.xml映射文件中<resultMap type="person" id="personResultMap" >,这里的person别名就代表该类了,如果没有前面的别名绑定,我们在所有需要类型type指定com.mmm.pojo.Person的时候,都需要写全名。另外MyBatis中本身已经绑定了许多类似的别名,在Configuration类的构造中已经预先注册绑定了相关别名,这也是我们在用Spring集成开发的配置文件中那些特殊别名能得到解析的关键所在,如下所示

public Configuration() {
    typeAliasRegistry.registerAlias(
"JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);     typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); typeAliasRegistry.registerAlias("FIFO", FifoCache.class); typeAliasRegistry.registerAlias("LRU", LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class);    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); }

  mappers(映射器)

<mappers>
        <mapper resource="com/xxx/xxx/mapper/xxxMapper.xml" />
</mappers>

  以上即为一个映射器配置样式,通过该配置,Mybatis会找到相应的SQL映射文件(前面环境搭建篇中为personMapper.xml),下面详细介绍SQL映射文件。

  MyBatis真正的力量

  The true power of MyBatis is in the Mapped Statements(MyBatis真正的力量就在SQL映射语句里)。这是MyBatis对SQL映射文件作介绍的第一句话,足以体现其重要性,前面说到的半自动的MyBatis需要自己写SQL语句,这个写SQL语句的地方就在这里。

  根节点mapper有一个属性namespace(命名空间),对应的是Mapper接口的全名,通过这个属性值的设置,MyBatis才能找到该SQL映射文件对象Mapper接口,从而构造相应的Mapper实例对象。  

  以上为MyBatis的SQL映射xml文件的元素结构,子节点中还有一个parameterMap,不过已经被废弃了,就没画上去。前面四个,看单词意思,应该不难理解,分别用于定义增删改查SQL语句。下面以查询<select>为例

<!-- 根据主键id查找记录 -->
<select id="selectById" resultType="person">
        select * from `TBL_PERSON` where id = #{id}
</select>    

  上面select节点中id为该节点的唯一标识,与其它节点区分,另一方面,id名对应我们的Mapper接口中的方法名,在这里对应PersonMapper的如下方法

//根据主键id查找Person对象
Person selectById(String id);

  由于是查询,会有返回数据内容,基于ORM思想,这里的T_PERSON表返回记录有一个实体类与之对应,那么具体对应哪个实体类怎么指定?这里的resultType即指定返回数据对应类型,并且用到了别名,所以这里其实就是指定了com.mmm.pojo.Person类作为对应实体类。另外还有一种resultMap定义方式,也可以指定返回类型,两者不能同时使用,下面会具体讲到。如果方法没有返回值,也可以不指定类型。

  节点中的语句即为该方法对应的SQL语句,这里的#{id}表示参数,对应PersonMapper接口中对应方法的参数String id。

   另外三种<delete><updata><insert>类似,不过要注意,<insert>作为插入节点,有几个特殊属性,useGeneratedKeys、keyProperty、keyColumn,这三个属性都是<insert>特有的,也仅对其有效,keyProperty属性指定数据表主键值对应的实体类属性,这里为Person类中的id;keyColumn属性指定数据表主键字段名,这里为id;useGeneratedKeys指定是否使用数据表主键策略生成的主键值,例如在Mysql中主键可以设置自增,然后我们在插入记录(或者说是写SQL语句)时,即使不指定主键值,插入也会成功,并且主键会自动赋值。为了以示区别,文中构建示例我会使用MySQL的自增主键,而不是之前的随机字符串作为主键。

  <sql>被用来定义可重用的SQL代码段,可以包含在其他语句中。

  再来看<resultMap>,之前的Person实例中,数据表的字段名和实体类属性名都是一样的,id,name,gender。如果不一样的话,我们再采用之前的写法就会有问题了,resultMap给我们提供了解决办法,下面我们通过完整构建一个Mybatis环境来演示整体内容。

  MyBatis实践

  这里我们单独讲Mybatis,并未使用Spring集成环境,也并未用到web层的Spring MVC,所以不需要构建web项目。所以首先通过maven新建一个java项目,pom.xml依赖可以参考前面的SSM环境搭建,最后项目结构大致如下图所示

  数据库中准备一张员工表TBL_EMP,三个字段:主键id自增,姓名emp_name,性别emp_gender,插入若干数据,如下图

  实体类(Emp.java)

package com.mmm.pojo;
//员工实体类
public class Emp {
    private Integer id;            //主鍵
    private String name;        //员工姓名
    private String gender;        //员工性别
    
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
}

  配置文件(mybatis-config.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>
    <!-- 这里可以定义类的别名,在mapper.xml文件中应用会方便很多 -->
    <typeAliases>
        <typeAlias alias="emp" type="com.mmm.pojo.Emp" />
    </typeAliases>
    <!-- 环境配置 -->
    <environments default="envir">
        <environment id="envir">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/ssm?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/mmm/mapper/empMapper.xml"/>
    </mappers>
</configuration>

  Mapper接口(EmpMapper.java)

package com.mmm.mapper;

import java.util.List;

import com.mmm.pojo.Emp;

public interface EmpMapper {
    
    //新增一个Emp对象
    void insert(Emp p);
    
    //根据主键id删除Emp对象
    void deleteById(Integer id);
    
    //修改一个Emp对象
    void update(Emp p);
    
    //根据主键id查找Emp对象
    Emp selectById(Integer id);
    
    //查找所有Emp对象,返回集合类型
    List<Emp> selectAll();
}

  SQL映射文件(EmpMapper.xml)

<?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命名空间绑定Mapper接口 -->
<mapper namespace="com.mmm.mapper.EmpMapper">
    
    <!-- resultMap定义,property对应实体类中属性,column对应数据表字段名 -->
    <resultMap type="emp" id="empResultMap" >
        <id property="id" column="id"></id>
        <result property="name" column="emp_name"></result>
        <result property="gender" column="emp_gender"></result>
    </resultMap>
    
    <!-- 新增一条记录,这里并未在SQL语句中设置主键id值 -->
    <insert id="insert" parameterType="emp" useGeneratedKeys="true" keyProperty="id" keyColumn="id"  >
        insert into `TBL_EMP`(emp_name,emp_gender) values (#{name},#{gender})
    </insert>
    
    <!-- 删除一条记录 -->
    <delete id="deleteById">
        delete from `TBL_EMP` where id = #{id}
    </delete>
    
    <!-- 更新一条记录 -->
    <update id="update" parameterType="EMP">
        update `TBL_EMP` set emp_name = #{name}, emp_gender = #{gender}
    </update>
    
    <!-- 查找所有记录 -->
    <select id="selectAll" resultMap="empResultMap">
        select * from `TBL_EMP`
    </select>
    
    <!-- 根据主键id查找记录 -->
    <select id="selectById" resultMap="empResultMap">
        select * from `TBL_EMP` where id = #{id}
    </select>
</mapper>

  测试(TestMyBatis.java)

package com.mmm.test;

import java.io.IOException;
import java.io.Reader;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import com.mmm.mapper.EmpMapper;
import com.mmm.pojo.Emp;

public class TestMyBatis {
    
    
    
    @Test
    public void testCore() throws IOException {
        //直接实例SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //MyBatis配置文件路径
        String path = "mybatis-config.xml";
        //通过路径获取输入流
        Reader reader = Resources.getResourceAsReader(path);
        //通过reader构建sessionFactory
        SqlSessionFactory sessionFactory = builder.build(reader);
        //获取SqlSession对象
        SqlSession sqlSession = sessionFactory.openSession();
        //获取Mapper实例
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        
        //获取所有记录并遍历展示
        List<Emp> list = mapper.selectAll();
        for(Emp emp:list) {
            System.out.println("姓名:"+emp.getName()+",性别:"+emp.getGender());
        }
        
    }
}

  运行程序成功,结果如下

  小结

  本文主要介绍了单独以MyBatis构建数据库访问程序的方法步骤,涉及的都是MyBatis基础和核心的内容,数据库也是针对的单表操作。MyBatis的关联查询、动态SQL、事务管理和Spring集成MyBatis等内容准备梳理一番了之后写出来。

posted @ 2017-12-04 00:25  窗外天空晴朗  阅读(3135)  评论(2编辑  收藏  举报