MyBatis系列二 开发环境准备
一、下载 MyBatis
输入网址 https://github.com/mybatis/mybatis-3/releases 进入 MyBatis 的官网,我们就可 下载MyBatis,如图2.1所示。
我们可以在这里下载到MyBatis所需的jar包和源码包。讲解MyBatis运行原理和插件的时候常常会用到源码的内容。
使用 MyBatis 项目可以参考 http://mybatis.org/mybatis-3/zh/index.html。
使用 MyBatis-Spring 项目可以参考 http://mybatis.org/spring/zh/index.html。
二、搭建幵发环境
无论使用哪一种Java IDE都可以轻松搭建开发环境。这里以Eclipse为例搭建我们的开发环境。我们打开下载得到的MyBatis开发包就可以得到如图所示的目录。
这里的jar文件分为两类l类是MyBatis本身的jar包,另一类在lib文件夹里0MyBatis 项目所依赖的jar包,而pdf文件则是它提供的API文档。我们只需要在Eclipse中引入 MyBatis的jar包即可,如图所示。
这样便完成了 MyBatis的环境搭建,我们便可以在项目中使用MyBatis 了。
三、MyBatis的基本构成
认识往往是从表面现象到内在本质的一个探索过程,所以对于MyBatis的掌握,我们 从认识“表面现象” ——MyBatis的基本构成开始。我们先了解一下MyBatis的核心组件。
- SqlSessionFactoryBuilder (构造器):它会根据配置信息或者代码来生成 SqlSessionFactory (工厂接口)。
- SqlSessionFactory:依靠工厂来生成 SqlSession (会话)。
- SqlSession:是一个既可以发送SQL去执行并返回结果,也可以获取Mapper的接口。
- SQL Mapper:它是MyBaits新设计的组件,它是由一个Java接口和XML文件(或 注解)构成的,需要给出对应的SQL和映射规则。它负责发送SQL去执行,并返 回结果。
用一张图表达它们之间的关联,如图2.4所示。
这里我们不需要马上明白MyBatis的组件内容,先了解它们之间的先后顺序、流程和 基本功能,后面我们会详细讨论它们的用法。
四、构建 SqlSessionFactory
每个MyBatis的应用都是以SqlSessionFactory的实例为中心的。SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获得。但是读者们需要注意SqlSessionFactory是一个工厂接口而不是现实类,它的任务是创建SqlSession。SqlSession类似于一个JDBC的 Connection对象。MyBatis提供了两种模式去创建SqlSessionFactory: 一种是XML配置的 方式,这是笔者推荐的方式;另一种是代码的方式。能够使用配置文件的时候,我们要尽 量的使用配置文件,这样一方面可以避免硬编码(hard code), 一方面方便日后配置人员的 修改,避免重复编译代码。
这里我们的 Configuration 的类全限定名为 org.apache.ibatis.session. Configuration,它在MyBatis中将以一个Configuration类对象的形式存在,而这个对象将存在于整个MyBatis应用的生命期中,以便重复读取和运用。在内存中的数据是计算机系统中读取速度最快的, 我们可以解析一次配置的XML文件保存到Configuration类对象中,方便我们从这个对象 中读取配置信息,性能高。单例占用空间小,基本不占用存储空间,而且可以反复使用。Configuration类对象保存着我们配置在MyBatis的信息。在MyBatis中提供了两个 SqlSessionFactory 的实现类,DefaultSqlSessionFactory 和 SqlSessionManager。不过 SqlSessionManager 目前还没有使用,MyBatis 中目前使用的是 DefaultSqlSessionFactory。让我们看看它们的关系图,如图2.5所示。
1、使用XML方式构建
这里我们配置一个简易的XML,包含获取数据库连接实例的数据源(DataSource)、决 定事务范围和控制方式的事务管理器(TransactionManager)和映射器(SQL Mapper)。 XML 配置文件的内容后面会详细探讨,这里先给出一个简单的示例,如代码清单所示。
<?xml version=nl.0n encoding=nUTF-8n ?> <!DOCTYPE configuration PUBLIC n-//mybatis.org//DTD Config 3.0//EN” ”http://mybatis•org/dtd/mybatis-3-config•dtdn> <configuration> <! --定义别名--〉 <typeAliases> <typeAlias alias=nrolen type=ncom.learn.chapter2.po.Rolen/> </typeAliases> <! --定义数据库信息,默认使用development数据库构建环境--> <environments default=”development”〉 <environment id="development”〉 。一釆用jdbc事务管理--〉 <transactionManager type=nJDBCn/> 配置数据库链接信息--〉 <dataSource type=nPOOLED11 > <property name=ndrivern value=ncom.mysql•jdbc.Drivern/> <property name=nurln value=njdbc:mysql://localhost:3306/mybatisn/> <property name=nusernamen value=nrootn/> <property name=npassword11 value="learnn/> </dataSource> </environment> </environments> <!--定义映射器--〉 <mappers> <mapper resource=ncom/learn/chapter2/mapper/roleMapper.xmln/> </mappers> 〈/configuration〉
对上面的配置做一下说明。
•这里配置了一个别名role,它代表com.leam.chapter2.po.Role,这样我们就可以在 MyBatis ±下文中引用它了。
•我们配置了环境内容,它默认使用id是development的环境配置,包含以下两方 面的内容。
(1) 釆用JDBC的事务管理模式。
(2) 数据库的连接信息。
•配置映射器。
这里引入了一个XML,它的作用是提供SQL和SQL对POJO的映射规则定义,它包 含了映射器里面的信息。MyBatis将解析这个XML,来为我们生成映射器。
现在我们用代码实现创建SqlSessionFactory,如代码清单2-2所示。
String resource = "mybatis-config.xml"; Inputstream inputstream = Resources.getResourceAsStream(resource);
SqlSessionFactory SqlSessionFactory = null; SqlSessionFactory = new SqlSessionFactoryBuilder().build (inputstream);
这里我们创建了一个XML文件输入流,用SqlSessionFactoryBuilder读取XML的信息 来创建SqlSessionFactory的对象。
MyBatis的解析程序会将mybatis-config.xml文件配置的信息解析到Configuration 类对象里面,然后利用SqlSessionFactoryBuilder读取这个对象为我们创建SqlSession Factory。
2、使用代码方式构建
除了使用XML配置的方式创建代码外,也可以使用Java编码来实现,不过并不推荐 这个方式,因为修改环境的时候,我们不得不重新编译代码,这样不利于维护。
不过在本书我们依旧讨论其实现方法。和上面XML方式一样我们也要配置别名、数 据库环境和映射器。MyBatis已经为我们提供好了对象的类和方法,我们只要熟悉它们的 使用即可。首先,构建Configuration类对象。然后,往对象里面注册我们构建 SqlSessionFactory所需要的信息便可。
让我们看看代码是如何现实的,如代码清单所示。
//构建数据库连接池 PooledDataSource dataSource = new PooledDataSource (); dataSource • setDriver (ncom.mysql . jdbc • Driver11); dataSource . setUrl (n jdbc :mysql: //localhost: 3306/mybatis,f); dataSource•setUsername(nrootn); dataSource.setPassword(Hlearnn); //构建数据库事务方式 TransactionFactory transactionFactory = new JdbcTransactionFactory(); //创建了数据库运行环境 Environment environment = new Environment ("developmentn, transactionFactory, dataSource); //构建 Configuration 对象 Configuration configuration = new Configuration(environment); //注册一个MyBatis上下文别名 configuration.getTypeAliasRegistry().registerAlias(nroleH, Role.class); //加入一个映射器 configuration.addMapper(RoleMapper.class); //使用 SqlSessionFactoryBuiIder 构建 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuiIder () .build (configuration); return sqlSessionFactory;
让我们说明一下上面的代码做了什么。
•初始化了一个数据库连接池。
•定义了 JDBC的数据库事务管理方式。
•用数据库连接池和事务管理方式创建了一个数据库运行环境,并命名为
development o
•创建了一个Configuration类对象,并把数据库运行环境注册给它。
•注册一个role的别名。
•加入一个映射器。
•用 SqlSessionFactoryBuilder 通过 Configuration 对象创建 SqlSessionFactory。
显然用代码方式和用XML方式只是换个方法实现而已,其本质并无不同。采用代码 方式一般是在需要加入自己特性的时候才会用到,例如,数据源配置的信息要求是加密的 时候,我们需要把它们转化出来。在大部分的情况下,笔者都不建议你使用这个方式来创 建 MyBatis 的 SqlSessionFactory 。
五、创建 SqISession
SqlSession是一个接口类,它类似于你们公司前台的美女客服,它扮演着门面的作用, 而真正干活的是Executor接口,你可以认为它是公司的工程师。假设我是客户找你们公司 干活,我只需要告诉前台的美女客服(SqlSession)我要什么信息(参数),要做什么东西,过段时间,她会将结果给我。在这个过程中,作为用户的我所关心的是:
(1) 要给美女客服(SqlSession)什么信息(功能和参数)。
(2) 美女客服会返回什么结果(Result)。
而我不关心工程师(Executor)是怎么为我工作的,只要前台告诉工程师(Executor),工程师就知道如何为我工作,这个步骤对我而言是个黑箱操作。
在 MyBatis 中 SqlSession 接 口的实现类有两个,分别是 DefaultSqlSession 和 SqlSessionManager。这里我们暂时不深入讨论Executor接口及其涉及的其他类,只关心SqlSession 的用法就好。我们构建了 SqlSessionFactory,然后生成MyBatis的门面接口 SqlSession。SqlSession接口类似于一个JDBC中的Connection接口对象,我们需要保证每次用完正常 关闭它,所以正确的做法是把关闭SqlSession接口的代码写在finally语句中保证每次都会关闭SqlSession,让连接资源归还给数据库。如果我们不及时关闭资源,数据库的连接资源将很快被耗尽,系统很快因为数据库资源的匮乏而瘫痪。让我们看看实现的伪代码,如代码清单所示。
//定义 SqlSession SqlSession sqlSession = null; try ( //打开SqlSession会话 sqlSession = sqlSessionFactory.openSession(); //some code ..•• sqlSession•commit(); } catch(Exception ex) ( . System.err.printIn(ex.getMessage()); sqlSession.rollback(); }finally ( //在finally语句中确保资源被顺利关闭 if (sqlSession != null) ( sqlSession.close(); ) }
这样SqlSession就被我们创建出来了,在finally语句中我们保证了它的合理关闭,让 连接资源归还给数据库连接池,以便后续使用。
SqlSession的用途主要有两种。
(1) 获取映射器,让映射器通过命名空间和方法名称找到对应的SQL,发送给数据库 执行后返回结果。
(2) 直接通过命名信息去执行SQL返回结果,这是iBatis版本留下的方式。在SqlSession 层我们可以通过update> insert、select、delete等方法,带上SQL的id来操作在XML中配置好的SQL,从而完成我们的工作;与此同时它也支持事务,通过commit> rollback方法 提交或者回滚事务。
关于这两种用途我们会在映射器里面进行讨论,这两种用途的优劣我们也会进行研讨。
六、映射器
映射器是由Java接口和XML文件(或注解)共同组成的,它的作用如下。
•定义参数类型。
•描述缓存。
•描述SQL语句。
•定义查询结果和POJO的映射关系。
一个映射器的实现方式有两种,一种是通过XML文件方式实现,读者应该记得我们 在mybatis-config.xml文件中已经描述了一个XML文件,它是用来生成Mapper的。另外 一种就是通过代码方式实现,在Configuration里面注册Mapper接口(当然里面还需要我 们写入Java注解)。当然它也是MyBatis的核心内容,同时也是最为复杂的。这两种方式都可以实现我们的需求,不过笔者强烈建议使用XML文件配置方式,理由如下。
• Java注解是受限的,功能较少,而MyBatis的Mapper内容相当多,而且相当复杂, 功能很强大,使用XML文件方式可以带来更为灵活的空间,显示出MyBatis功能的强大和灵活。
•如果你的SQL很复杂,条件很多,尤其是存在动态SQL的时候,写在Java文件里 面可读性较差,增加了维护的成本。
所以本书主要介绍XML文件方式,而事实上使用注解也是可以完成SQL定义的,包 括动态SQL定义。但是使用SQL构造器可读性不佳且工作量巨大、实操性弱,因此本书 不介绍这种方式。
1、XML文件配置方式实现Mapper
使用XML文件配置是MyBatis实现Mapper的首选方式。它由一个Java接口和一个 XML文件构成。让我们看看它是如何实现的。
第一步,给出Java接口,如代码清单所示。
package com.learn•chapter2.mapper; import com.learn.chapter2.po.Role; public interface RoleMapper ( public Role getRole(Long id); }
这里我们定义了一个接口,它有一个方法getRole,通过角色编号找到角色对象。
第二步,给出一个映射XML文件,如代码清单所示。
<?xml version=nl.0" encoding=”UTF-8” ?> <!DOCTYPE mapper PUBLIC ,f-//mybatis.org//DTD Mapper 3.0//EN”"http://mybatis•org/dtd/mybatis-3-mapper.dtdn> <mapper namespace="com.learn.chapter2.mapper.RoleMapper"> <select id=ngetRolen parameterType="long" resultType="role">
select id, role_name as roleName, note from t_role where id = # (id}
</select> </mapper>
描述一下上面的XML文件做了什么。
•这个文件是我们在配置文件mybatis-config.xml中配置了的,所以MyBatis会读取这 个配置文件,生成映射器。
•定义了一个命名空间为 com.leam.chapter2.mapper.RoleMapper 的 SQL Mapper,这个 命名空间和我们定义的接口的全限定名是一致的。
•用一个select元素定义了一个查询SQL, id为getRole,和我们接口方法是一致的, 而parameterType则表示我们传递给这条SQL的是一个java.lang.Long型参数,而 resultType则定义我们需要返回的数据类型,这里为role,而role是我们之前注册 com.leam.chapter2.po.Role 的另U 名。
我们来看看这个P0J0,如代码清单所示。
package com.learn•chapter2.po; public class Role ( private Long id; private String roleName; private String note; public Long getld() ( return id; } public void setld(Long id) ( this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) ( this•roleName = roleName; ) public String getNote() ( return note; ) public void setNote(String note) ( this.note = note; } }
这是一个十分简单的POJO,符合Java Bean的规范。下面是SQL的代码。
select id, role name as roleName, note from t role where role no = #{id}
#{id}为这条SQL的参数。而SQL列的别名和POJO的属性名称保持一致。那么MyBatis 就会把这条语句查询的结果自动映射到我们需要的POJO属性上,这就是自动映射。我们 可以用SqlSession来获取这个Mapper,代码也比较简单,如代码清单2.8所示。
这样就完成了 MyBatis的一次查询。
2、Java注解方式实现Mapper
Java注解方式实现映射方法不难,只需要在接口中使用Java注解,注入SQL即可。 我们不妨看看这个接口,如代码清单所示。
package com.learn.chapter2.mapper; import org.apache.ibatis.annotations.Select; import com.learn.chapter2.po.Role; public interface RoleMapper2 ( @Select (value="select id, role name as roleName, note from t_role where id = #(id}") public Role getRole(Long id); }
这里笔者解释一下,我们使用了@Select注解,注入了和XML 一样的select元素,这 样MyBatis就会读取这条SQL,并将参数id传递进SQL。同样使用了别名,这样MyBatis会为我们自动映射,得到我们需要的Role对象。这里看起来比XML简单,但是现实中我 们遇到的SQL远比例子复杂得多。如果多个表的关联、多个查询条件、级联、条件分支等 显然这条SQL就会复杂得多,所以笔者并不建议读者使用这种方式。比如代码清单所示的这条SQL。
select u.id as userid, u.username, u.mobile, u.email, r.id as roleid, r•role_name from t_username u left join t_user_role ur on u.id = ur.user_id left join t_role r on ur.role_id = r.id where 1=1 and u.username like concat (1 % 1, #(username}z 1 % 1) and u.mobile like concat(1 % 1 , #{mobile}9 1 % 1) and u.email like concat (f % 1z #(email}9 1 % 1) and r.role name like concat(1 % 1, #(roleName), '%')
如果我们只要写这条SQL还可以接受,但是如果我们还需要根据上下文判断where语句后面的条件语句,那么显然代码会相当的复杂。我们需要判断username是否为空,如果为空我们就不以username作为条件,这时候SQL就不要出现下面的语句。
and u.username like concat%, #{username},'号')
同样的,如果mobile> email> role name都需要这样判断,都写在Java文件的注解里 面,会十分复杂,造成可读性下降。当然如果你的系统很简单,使用注释方式也不失为一个好办法,毕竟简易许多。我们只需要加入这一段代码即可注册这个接口为映射器,如下所示。
configuration.addMapper(RoleMapper2.class);
3、一些疑问
在MyBatis中保留着iBatis,通过“命名空间(namespace)+SQL id”的方式发送SQL并 返回数据的形式,而不需要去获取映射器,以下面的代码为例。
Role role=sqlSession.selectOne ("com.learn•chapter2.mapper.RoleMapper.getRole","role_no_l" —— ——> 1L;
如果MyBatis上下文中只有一个SQL的id为getRole,那么我们将代码简写为:
Role role = sqlSession.selectOne ("getRole","role_no_l" -- --> 1L;
注意,当SQL的id有两个或两个以上getRole的时候,第二种省略的办法就会失败。系统异常就会提示你写出“命名空间+SQLid”的全路径模式才可以。其实它们大同小异, 都是发送SQL并返回需要的结果,而MyBatis 一样会根据"com.leam.chapter2.mapper.RoleMapper.getRole"找到需要执行的接口和方法,进而找到对应的SQL,传递参数 "role_no_1" - -> 1L到SQL中,返回数据,完成一次查询。
那么困惑是我们需要Mapper吗?答案是肯定的,Mapper是一个接口,相对而言它可 以进一步屏蔽SqlSession这个对象,使得它具有更强的业务可读性。因此笔者强烈建议釆 用映射器方式编写代码,其好处主要有两点。
• sqlSession.selectOne是功能性代码,长长的字符串比较晦涩难懂,不包含业务逻辑 的含义,不符合面向对象的规范,而对于roleMapper.getRole这样的才是符合面向 对象规范的编程,也更符合业务的逻辑。
•使用Mapper方式,IDE可以检查Java语法,避免不必要的错误。
这是MyBatis特有的接口编程模式,而iBatis只能通过SqlSession用SQL的id过滤SQL 去执行。
我们使用的仅仅是Java接口和一个XML文件(或者注解)去实现Mapper, Java接口 不是实现类,对于Java语言不熟悉的读者肯定会十分疑惑,一个没有实现类的接口怎么能 够运行呢?其实它需要运用到Java语言的动态代理去实现,而实现Java语言的动态代理的 方式有多种。这里我们还是集中于它的用法,所以可以这样理解:我们会在MyBatis上下 文中描述这个接口,而MyBatis会为这个接口生成代理类对象,代理对象会根据“接口全 路径+方法名”去匹配,找到对应的XML文件(或注解)去完成它所需要的任务,返回我 们需要的结果。
关于SqlSession和Mapper是MyBatis的核心内容和难点,它内部远远没有我们目前看 到的那么简单,只是在入门阶段我们暂时不需要讨论它的实现方式,知道它的作用和用法 就可以了。
七、生命周期
我们在前面章节讨论了 MyBatis的主要组件和它们的基本用法,而现实中要想写出高效的 程序只掌握Mybatis的基本用法是远远不够的。我们还要掌握它们的生命周期,这是十分重要的,尤其是在Web应用,Socket连接池等多线程场景中,如果我们不了解MyBatis组件的 生命周期可能带来很严重的并发问题。这节的任务是正确理解SqlSessionFactoryBuilder> SqlSessionFactory> SqlSession和Mapper的生命周期,并且重构上面的代码,使MyBatis 能够高效的工作。这对于MyBatis应用的正确性和高性能是极其重要的,我们必须掌握 它们。
1、SqISessionFactoryBuilder
SqlSessionFactoryBuilder 是利用 XML 或者 Java 编码获得资源来构建 SqlSessionFactory 的,通过它可以构建多个SessionFactory。它的作用就是一个构建器,一旦我们构建了 SqlSessionFactory,它的作用就已经完结,失去了存在的意义,这时我们就应该毫不犹豫的 废弃它,将它回收。所以它的生命周期只存在于方法的局部,它的作用就是生成 SqlSessionFactory 对象。
2、SqlSessionFactory
SqlSessionFactory的作用是创建SqlSession,而SqlSession就是一个会话,相当于JDBC 中的Connection对象。每次应用程序需要访问数据库,我们就要通过SqlSessionFactory创 建SqlSession,所以SqlSessionFactory应该在MyBatis应用的整个生命周期中。而如果我们 多次创建同一个数据库的SqlSessionFactory,则每次创建SqlSessionFactory会打开更多的数 据库连接(Connection)资源,那么连接资源就很快会被耗尽。因此SqlSessionFactory的责 任是唯一的,它的责任就是创建SqlSession,所以我们果断釆用单例模式。如果我们采用多例,那么它对数据库连接的消耗是很大的,不利于我们统一的管理,这样便嗅到了代码的 坏味道。所以正确的做法应该是使得每一个数据库只对应一个SqlSessionFactory,管理好 数据库资源的分配,避免过多的Connection被消耗。
3、SqlSession
SqlSession是一个会话,相当于JDBC的一个Connection对象,它的生命周期应该是 在请求数据库处理事务的过程中。它是一个线程不安全的对象,在涉及多线程的时候我们 需要特别的当心,操作数据库需要注意其隔离级别,数据库锁等高级特性。此外,每次创 建的SqlSession都必须及时关闭它,它长期存在就会使数据库连接池的活动资源减少,对系统性能的影响很大。正如前面的代码一样,我们往往通过finally语句块保证我们正确 的关闭SqlSession0它存活于一个应用的请求和操作,可以执行多条SQL,保证事务的一致性。
4、Mapper
Mapper是一个接口,而没有任何实现类,它的作用是发送SQL,然后返回我们需要的结果,或者执行SQL从而修改数据库的数据,因此它应该在一个SqlSession事务方法之内, 是一个方法级别的东西。它就如同JDBC中的一条SQL语句的执行,它最大的范SqlSession是相同的。尽管我们想一直保存着Mapper,但是你会发现它很难控制,所以尽 量在一个SqlSession事务的方法中使用它们,然后废弃掉。
有了上面的叙述,我们己经清楚了 MyBatis组件的生命周期,如图2.6所示。
到这里我们比较完整地描述了 MyBatis框架和它所涉及的组件及其生命周期,接下来 我们来构建一个简单的实例,以加深对MyBatis基础框架的理解。
八、实例
这里做一个实例,它可以使我们熟悉MyBatis主要组件的用法。我们需要满足MyBatis各个组件的生命周期。首先需要生成SqlSessionFacotry单例,然后让它生成SqlSession,进而拿到映射器中来完成我们的业务逻辑。确定文件所需要放置的路径,以便在上下文中读取配置文件,如图2.7所示。
各个实例文件的作用,如表2-1所示
首先,提供log4j的配置文件,如代码清单2-11所示。它为我们打出MyBatis的运行 轨迹,给我们调试MyBatis应用带来了极大的帮助。
log4j.rootLogger=DEBUG , stdout log4j•logger.org.mybatis=DEBUG log4j.appender.stdout=org.apache.Iog4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.Iog4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
其次,构建SessionFactory,我们需要配置文件,如代码清单所示。
<?xml version=nl.0" encoding=nUTF-8n?> <!DOCTYPE configurationPUBLIC H-//mybatis.org//DTD Config 3.0//EN” "http://mybatis•org/dtd/mybatis-3-config•dtd”> <configuration> <typeAliases> <typeAlias alias=Hrolen type=Hcom. learn . chapter2 .po. Role,f/> </typeAliases> <environments defau 11=11 deve 1 opmentn> <environment id=Hdevelopmentn> <transactionManager type=HJDBCH> <property name= HautoCommitn value=,,falsen/> </transactionManager> <dataSource type=11 POOLED11 > <property name=,,driverH value=,*00111.mysql . jdbc • DriverH/> <property name=nurln value=fljdbc:mysql://localhost:3306/mybatisH/> <property name=,fusernamen value=nrootff/> <property name=npasswordn value=nlearnn/> </dataSource> </environment> </environments> <mappers> <mapper resource=Hcom\learn\chapter2\mapper\roleMapper•xml,f/> </mappers> </configuration>
最后,构建SqlSessionFactory,并且给出创建SqlSession的方法,这里我们利用配置文 件mybatis-config.xml完成SqlSessionFactory的构建。这里和上面有些变化,我们不妨先看看代码如何现实,如代码清单所示。
package com.learn.chapter2.util; import java.io•lOException; import java•io.Inputstream; import java.util.logging.Level; import java.util•logging.Logger; org.apache.ibatis•io•Resources; org•apache.ibatis.session.SqlSession; org.apache•ibatis•session.SqlSessionFactory; org.apache.ibatis.session.SqlSessionFactoryBuilder; class SqlSessionFactoryUtil ( //SqlSessionFactory 对象 private static SqlSessionFactory SqlSessionFactory = null; 〃类线程锁 private static final Class CLASS_LOCK = SqlSessionFactoryUtil.class; *私有化构造参数 ★ / private SqlSessionFactoryUtil() () *构建 SqlSessionFactory ★/ public static SqlSessionFactory initSqlSessionFactory() ( String resource = nmybatis-config • xml,f; Inputstream inputstream = null; try ( inputstream = Resources.getResourceAsStream(resource); } catch (lOException ex) ( Logger.getLogger(SqlSessionFactoryUtil•class•getName()).log(Level•SEVEREz null, ex); } synchronized(CLASS_LOCK) ( if (SqlSessionFactory == null) ( sqlSessionFactory=new SqlSessionFactoryBuilder().build Stream); } } return SqlSessionFactory; 打开 SqlSession public static SqlSession openSqlSession() ( if (SqlSessionFactory == null) ( initSqlSessionFactory(); return sqlSessionFactory.openSession(); } }
解释一下代码的含义,正如生命周期描述的一样,我们希望SqlSessionFactory对于一 个数据库而言只有一个实例,于是我们希望它是单例。现在我们学习一下代码如何实现单例模式。
构建SessionFactory单例是通过方法initSqlSessionFactory实现的。首先,将构造方法 设置为私有(private),其目的是避免使用者使用new的方式去创建多个对象。然后,我们 使用synchronized对SqlSessionFactoryUtil类加锁,其目的是避免在多线程环境(MyBatis 多用于多线程环境,比如Web服务器,Socket请求等)中,多次初始化造成对象的不唯一。某一个对象在应用中承担唯一责任的时候使用单例模式,而本例中SqlSessionFactory 的唯一责任就是为我们创建SqlSession,所以采用单例模式。单例模式的好处在于可以重复使用这个唯一对象,而对象在内存中读取和运行速度都比较快,同时节约内存。我们的办 法往往是把构造方法私有化,并给一个静态(static)方法,让其返回唯一单例,而在多线 程环境初始化单例,往往需要加线程锁以避免类对象被多次初始化,正如本例一样。
我们还实现了 openSqlSession方法,利用构建好的SqlSessionFactory创建SqlSession, 为将来所用奠定基础。
这里我们要给一个POJO类——Role.java,如代码清单所示,它就是一个很普通的JavaBean。
package com.learn•chapter2.po; public class Role { private Long id; private String roleName; private String note; public Long getld() ( return id; ) public void setld(Long id) ( this.id = id; } public String getRoleName() ( return roleName; } public void setRoleName(String roleName) ( this•roleName = roleName; ) public String getNote() ( return note; ) public void setNote(String note) ( this.note = note; } }
我们还需要一个映射器的描述,让我们编写XML映射文件,如代码清单所示。
<?xml version=,f 1.0” encoding=nUTF-8n ?> <!DOCTYPE mapper PUBLIC n-//mybatis•org//DTD Mapper 3.0//EN” nhttp://mybatis.org/dtd/mybatis-3-mapper.dtdn> <mapper namespace=ncom.learn•chapter2.mapper.RoleMappern> <select id=,,getRole" parameterType=nlongf, resultType=nrolen> select id, role_name as roleName, note from t_role where id = # {id} </select> <insert id=ninsertRolef, parameterType=nrolen> insert into t_role(role_name, note) values (#(roleName), #(note)) </insert> <delete id=ndeleteRolen parameterType=,,longn> delete from t_role where id = #(id) </delete> </mapper> .
这里我们定义了 3条SQL,它们的作用有3个。
•查询一个角色对象。
•插入角色。
•删除角色表的数据。
这时候需要定义一个接口,注意接口的方法要和XML映射文件的id保持一致,于是得出下面的接口,如代码清单所示。
package com.learn.chapter2.mapper; import com.learn•chapter2.po.Role; public interface RoleMapper { public Role getRole(Long id); public int deleteRole(Long id); public int insertRole(Role role); }
为此我们已经构建好SqlSessionFactory,同时也提供了创建SqlSession的方法和映射 器,完成了对应的3个方法。现在我们可以利用这些东西完成下面两个任务了。
(1) 插入一^角色(Role)对象。
(2) 删除一个编号为1L的角色对象。
这里我们使用Chapter2Main.java类完成我们需要完成的任务,如代码清单所示。
package com.learn•chapter2.main; import java.io.lOException; java.io•Inputstream; import org•apache.ibatis.datasource.pooled.PooledDataSource; import org.apache.ibatis•io.Resources; import org.apache•ibatis.mapping.Environment; import org•apache.ibatis.session.Configuration; import org•apache•ibatis.session•SqlSession; import org.apache.ibatis•session.SqlSessionFactory; import org.apache.ibatis.session•SqlSessionFactoryBuilder; import org.apache.ibatis.transaction.TransactionFactory; import org.apache•ibatis.transaction.jdbc•JdbcTransactionFactory; import com.learn.chapter2.mapper•RoleMapper; import com.learn.chapter2.po.Role; import com.learn.chapter2.util.SqlSessionFactoryUtil; public class Chapter2Main{ public static void main(String[] args) throws lOException { SqlSession sqlSession = null;* try ( sqlSession = SqlSessionFactoryUtil.openSqlSession (); RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); Role role = new Role(); role.setRoleName(HtestNamen); role.setNote(HtestNoteH); roleMapper.insertRole(role); roleMapper.deleteRole(IL); sqlSession.commit(); ) catch(Exception ex) ( System.err.printIn(ex.getMessage ()); sqlSession.rollback(); } finally ( if (sqlSession != null) ( sqlSession.close(); } } }
至此我们的代码完成了,让我们用Java Application的形式运行一下Chapter2Main.javao 我们可以看到如下结果。
DEBUG 2016-02-01 14:48:10,528 org.apache•ibatis•transaction.jdbc. JdbcTransaction:Setting autocommit to false on JDBC Connection[com. mysql.jdbc•JDBC4Connection@7e0b0338] DEBUG 2016-02-01 14:48:10,531 org. apache. ibatis . logging, jdbc . BaseJdbcLogger: ==> Preparing: insert into t_role(role_namez note) values (?, ?) DEBUG 2016-02-01 14:48:10,563 JdbcLogger: ==> Parameters: testName DEBUG 2016-02-01 14:48:10,564 JdbcLogger:<==Updates:1 DEBUG 2016-02-01 14:48:10,564 JdbcLogger: ==> Preparing: delete DEBUG 2016-02-01 14:48:10,565 JdbcLogger:==> Parameters: 1(Long) DEBUG 2016-02-01 14:48:10,566 org•apache•ibatis•logging.jdbc•BaseJdbc Logger:<==Updates:1 DEBUG 2016-02-01 14:48:10,566 org.apache•ibatis•transaction.jdbc. JdbcTransaction: Committing JDBC Connection [com.mysql•jdbc.JDBC4Connec tion@7e0b0338]
这样Log4j为我们打印出了 MyBatis的运行轨迹,最为重要的是它为我们打印出了运 行的SQL和参数。在调试中这些信息是十分重要的,如果发生异常我们将可以找到问题的 所在,也能找到插入和删除角色的过程,运行完全成功。