iBatis开发详解(6)---Spring的数据库访问
2018-10-13 12:04 更新
现在我们介绍Spring对iBatis的支持。
相对于Hibernate等ORM框架的全自动SQL,那么iBatis则属于半自动化的ORM框架,我们需要编写SQL语句,由iBatis进行数据库访问,返回结果。而iBatis可以为我们做的更多,比如对查询结果的封装等等。虽然不如全自动SQL方便,但是SQL的主动权却在我们开发人员的手中,对SQL优化的掌控则是很直接的。对于Hibernate和iBatis的其它讨论,不是我们探究的范围。
当前iBatis的版本为3,其名称也已经更改为MyBatis。而Spring更新到3.1都没有对MyBatis进行支持,但是MyBatis团队已经自行开发了Spring的支持。我们以Spring为主,仍然使用对iBatis2的支持来进行说明。
首先是构建开发环境,我们仍然使用Maven作为构建环境,创建项目,本文的示例基于“联系人管理”功能来展示:
package org.ourpioneer.contact.bean;
public class Contact {
private Long id;
private String name;
private String gender;
private String mobile;
private String address;
//省略getter和setter方法
@Override
public String toString() {
return "Contact [id=" + id + ", name=" + name + ", gender=" + gender
+ ", mobile=" + mobile + ", address=" + address + "]";
}
}
编写一个Contact类来描述联系人的一些特征,为了简便,我们使用5个字段就够了,实体类是很简单的。下面是添加类库了,要访问数据库,不能少了数据库驱动,我们使用MySQL数据库,在pom.xml中添加依赖: <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.14</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
同时在数据库中建表,因为iBatis还没有自动建表的功能: CREATE TABLE `contact` (
`ID` int(11) NOT NULL AUTO_INCREMENT ,
`NAME` varchar(20) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL ,
`GENDER` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL ,
`MOBILE` varchar(11) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL ,
`ADDRESS` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL ,
PRIMARY KEY (`ID`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=latin1 COLLATE=latin1_swedish_ci
AUTO_INCREMENT=1
ROW_FORMAT=COMPACT
;
加好驱动,创建好数据库表,那么程序访问需要配置连接池,这里我们使用BoneCP连接池,在pom.xml中加入相关依赖: <repositories>
<repository>
<releases>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<updatePolicy>always</updatePolicy>
</snapshots>
<id>Jolbox</id>
<name>Jolbox Repositories</name>
<url>http://jolbox.com/bonecp/downloads/maven</url>
</repository>
</repositories>
因为BoneCP的Maven仓库不在Maven的中央仓库中,所以必须加入以上代码到pom.xml中,这点可以在BoneCP的官方网站(http://jolbox.com/)中获得,下面就是加入依赖了,就没有什么多说的了: <dependency>
<groupId>com.jolbox</groupId>
<artifactId>bonecp</artifactId>
<version>0.7.1.RELEASE</version>
</dependency>
下面配置系统日志,因为引入BoneCP而引入了slf4j,如果自动添加的版本过低,这里我们要重新设置slf4j的版本为1.6.4。那么我们添加logback来设置日志系统:<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
下面配置logback.xml来配置日志具体项,logback.xml文件需要放置到类路径的根路径下:<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<jmxConfigurator />
<!--输出到控制台,方便调试,应用时切换到文件Log -->
<appender name="ConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%date %-5level [%logger{80}:%L] - %msg%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 需要记录日志的包 -->
<logger name="org.ourpioneer">
<level value="debug" />
</logger>
<logger name="org.springframework">
<level value="warn" />
</logger>
<root>
<level value="warn" />
<appender-ref ref="ConsoleAppender" />
</root>
</configuration>
这里我们只创建了控制台的Appender,其中设置了日志的格式,编码和需要记录日志的包,并配置响应日志的级别,这都很好理解。下面是添加Spring和iBatis的类库:<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-sqlmap</artifactId>
<version>2.3.4.726</version>
<scope>compile</scope>
</dependency>
Spring的类库添加core,beans,context,jdbc和orm模块即可,其它依赖则会自动添加过来,最终的类库如下图所示: 下面就是构建项目的目录结构:
我们采用分层开发的思想,DAO控制访问数据,Service调用DAO方法,同时在Service层做事务,因为我们不是Web项目,这里没有Web层的框架和Action。Bean包下的实体类Contact前面解释过了,common包下的BaseDAO提供操作iBatis的方法,ParameterMap是我们为iBatis提供参数的对象,dao包下就是DAO对象,service包下放置Service类,sqlMaps包下放置iBatis用到的SQL映射文件。App类是我们的测试类。类路径的根目录下的beans.xml是Spring的配置文件,logback.xml是日志配置文件,sqlMapConfig.xml是iBatis的总配置文件,首先来看sqlMapConfig.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings cacheModelsEnabled="true" enhancementEnabled="true"
lazyLoadingEnabled="true" errorTracingEnabled="true" maxRequests="32"
maxSessions="10" maxTransactions="5" />
<sqlMap resource="org/ourpioneer/contact/sqlMaps/contact.xml" />
</sqlMapConfig>
其中就是对iBatis的一些设置,并包含sqlMap文件,很简单,这没有什么可多说的,logback文件前面给出了,下面是Spring的配置文件,首先配置数据源dataSource:<bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="123" />
<property name="maxConnectionsPerPartition" value="30" />
<property name="minConnectionsPerPartition" value="10" />
<property name="partitionCount" value="3" />
<property name="acquireIncrement" value="5" />
<property name="statementsCacheSize" value="100" />
<property name="releaseHelperThreads" value="3" />
</bean>
关于BoneCP的介绍网上也有很多,这里不再详细解释各个配置项的含义了,配置好数据源,下面是SqlMapClient,使用Spring的bean工厂来创建,参数是iBatis的总配置文件和数据源,这里我们需要注入配置好的数据源dataSource: <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:sqlMapConfig.xml" />
<property name="dataSource" ref="dataSource" />
</bean>
Spring在对iBatis的支持上提供了SqlMapClientTemplate,也就是操作SqlMapClient的模板,这个jdbcTemplate类似,其中的方法使用方式也是类似的,为我们操作iBatis提供了便捷的接口: <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<constructor-arg>
<ref bean="sqlMapClient" />
</constructor-arg>
</bean>
数据库操作的bean都配置好了,下面是事务模块的配置,我们抽象出Service层,也是为了方便事务操作,可能一组业务需要操作多个DAO,那么显然不能在DAO层做事务,而在业务层做事务是非常合适的,也符合逻辑,我们使用Spring提供的DataSourceTransactionManager来配置事务管理器: <bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
下面是事务拦截的方法和AOP切入点:<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="insert*" rollback-for="Exception" />
<tx:method name="add*" rollback-for="Exception" />
<tx:method name="addOrUpdate*" rollback-for="Exception" />
<tx:method name="del*" rollback-for="Exception" />
<tx:method name="update*" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod"
expression="execution(* org.ourpioneer.service.*Service.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
</aop:config>
这样,我们对org.ourpioneer.service.*Service中方法名以insert/add/del/update等开头的方法都做上事务了。因为我们使用Spring作为bean容器,那么将DAO和Service也纳入Spring 的管理之中:<bean id="baseDAO" class="org.ourpioneer.contact.common.BaseDAO">
<property name="sqlMapClientTemplate" ref="sqlMapClientTemplate" />
</bean>
<bean id="contactDAO" class="org.ourpioneer.contact.dao.impl.ContactDAOImpl"
parent="baseDAO" />
这里我们将sqlMapClientTemplate配置到BaseDAO中,而ContactDAO继承BaseDAO即可,同时这里要注意这里我们使用了接口和实现相分离的做法,class中是实现类而不是接口,下面是Service: <bean id="contactService" class="org.ourpioneer.contact.service.ContactService">
<property name="contactDAO" ref="contactDAO" />
</bean>
配置文件都没有问题了,我们需要把这些涉及到的类都给写出来: package org.ourpioneer.contact.dao;
import java.util.Map;
import org.ourpioneer.contact.bean.Contact;
public interface ContactDAO {
public Contact getContactById(Map<Object, Object> parameterMap);
}
DAO接口仅仅是方法的描述,而实现类中编写具体实现代码:package org.ourpioneer.contact.dao.impl;
import java.util.Map;
import org.ourpioneer.contact.bean.Contact;
import org.ourpioneer.contact.common.BaseDAO;
import org.ourpioneer.contact.dao.ContactDAO;
public class ContactDAOImpl extends BaseDAO implements ContactDAO {
public Contact getContactById(Map<Object, Object> parameterMap) {
return (Contact) getSqlMapClientTemplate().queryForObject("getContactById",
parameterMap);
}
}
这里涉及到了BaseDAO,我们来看一下: package org.ourpioneer.contact.common;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
public class BaseDAO {
private SqlMapClientTemplate sqlMapClientTemplate;
public SqlMapClientTemplate getSqlMapClientTemplate() {
return sqlMapClientTemplate;
}
public void setSqlMapClientTemplate(
SqlMapClientTemplate sqlMapClientTemplate) {
this.sqlMapClientTemplate = sqlMapClientTemplate;
}
}
调用DAO的Service代码如下:package org.ourpioneer.contact.service;
import java.util.Map;
import org.ourpioneer.contact.bean.Contact;
import org.ourpioneer.contact.common.ParameterMap;
import org.ourpioneer.contact.dao.ContactDAO;
public class ContactService {
private ContactDAO contactDAO;
public ContactDAO getContactDAO() {
return contactDAO;
}
public void setContactDAO(ContactDAO contactDAO) {
this.contactDAO = contactDAO;
}
public Contact getContactById(long id) {
Map<Object, Object> parameterMap = new ParameterMap("ID", id);
return getContactDAO().getContactById(parameterMap);
}
}
从Spring的配置文件我们不难理解这些代码的真正含义,就不过多解释,这里用到了一个ParameterMap对象,我们来看一下它的定义:package org.ourpioneer.contact.common;
import java.util.HashMap;
public class ParameterMap extends HashMap<Object, Object> {
private static final long serialVersionUID = 1L;
public ParameterMap(Object... parameters) {
for (int i = 0; i < parameters.length - 1; i += 2) {
super.put(parameters[i], parameters[i + 1]);
}
}
}
非常的简单,这样我们在使用的时候就可以按照[名,值,名,值....]这样的格式来放置参数了。本例中涉及到了一个查询叫“getContactById”,它定义在sqlMaps下的contact.xml中,我们来看一下: <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap>
<typeAlias alias="parameterMap" type="org.ourpioneer.contact.common.ParameterMap" />
<typeAlias alias="contact" type="org.ourpioneer.contact.bean.Contact" />
<select id="getContactById" parameterClass="parameterMap"
resultClass="contact">
select *
from contact
where ID=#ID:LONG#
</select>
</sqlMap>
首先对ParameterMap和Contact进行别名,为了下面的方便调用,这里我们有一个查询,SQL语句很好理解,select元素的id属性就是它的标识符了,也是sqlMapClientTemplate的queryForObject方法第一个参数,parameterClass指定了参数类,我们使用的是自定义的ParameterMap,resultClass结果类型是我们的实体类对象,这样我们执行这个查询,iBatis会自动为我们封装成一个bean返回,易于调用。 为了测试,我们首先在数据表中插入一条记录:
INSERT INTO `contact` VALUES ('1', 'Sarin', 'male', '15940912345', 'Dalian');
下面来编写测试代码: package org.ourpioneer.contact;
import org.ourpioneer.contact.bean.Contact;
import org.ourpioneer.contact.service.ContactService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans.xml");
ContactService contactService = (ContactService) context
.getBean("contactService");
Contact contact = contactService.getContactById(1);
logger.info("{}",contact);
}
}
我们从Spring的ApplicationContext中获取到Service对象,然后调用getContactById方法获取结果,下面是运行的结果: 我们继续来研究spring和iBatis的整合访问数据。下面首先来看看插入操作,数据的插入操作属于更新操作的一种,是比较简单的一种操作,就是将符合数据表字段规范且对应用程序无害的数据插入到数据表中。
我们分两种方式来进行插入操作,第一种是Bean的方式进行,首先来编写iBatis的插入SQL语句:
<insert id="insertContact" parameterClass="contact">
insert into
contact(NAME,GENDER,MOBILE,ADDRESS)
values
(#name:VARCHAR#,#gender:VARCHAR#,#mobile:VARCHAR#,#address:VARCHAR#)
</insert>
我们使用<insert>标签表示插入语句,为该语句起个名字,就是id属性中的insertContact,之后声明我们使用的参数类型,也就是我们声明过的Contact。需要注意的是这里的参数设置,#name:VARCHAR#,小写的name是Bean中的字段名称,大小写要完全一致才行,VARCHAR表示的是数据类型,不用多说。在数据表中的这四个字段我们都声明为VARCHAR类型,而插入时不需要设置ID,是因为我们使用MySQL数据库的自增主键了,如果是其它类型的数据库,请使用相应的主键生成策略来为主键赋值。 编写完SQL语句,我们来写DAO:
package org.ourpioneer.contact.dao;
import java.util.Map;
import org.ourpioneer.contact.bean.Contact;
public interface ContactDAO {
public Contact getContactById(Map<Object, Object> parameterMap);
public int insertContact(Contact contact);
}
我们多加了一个insertContact(Contact contact)方法,返回值类型是int,也就是更新的行数,这都是JDBC规范中的内容,下面来实现这个新加的方法: package org.ourpioneer.contact.dao.impl;
import java.util.Map;
import org.ourpioneer.contact.bean.Contact;
import org.ourpioneer.contact.common.BaseDAO;
import org.ourpioneer.contact.dao.ContactDAO;
public class ContactDAOImpl extends BaseDAO implements ContactDAO {
public Contact getContactById(Map<Object, Object> parameterMap) {
return (Contact) getSqlMapClientTemplate().queryForObject(
"getContactById", parameterMap);
}
public int insertContact(Contact contact) {
return getSqlMapClientTemplate().update("insertContact", contact);
}
}
该方法的实现也非常简单,但是注意这里,我们并没有使用sqlMapClientTemplate的insert方法,而是update方法来进行操作的。原因是insert方法不会返回int类型的结果,而insert也是更新操作的一种,使用update方法也是可以的。下面在Service类中对应添加代码: public int insertContact(Contact contact) {
return getContactDAO().insertContact(contact);
}
之后我们开始编写测试类,代码如下: package org.ourpioneer.contact;
import org.ourpioneer.contact.bean.Contact;
import org.ourpioneer.contact.service.ContactService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans.xml");
ContactService contactService = (ContactService) context
.getBean("contactService");
// Contact contact = contactService.getContactById(1);
Contact contact = new Contact("Tom", "male", "15940990001", "Dalian");
int recordsOfUpdates = contactService.insertContact(contact);
logger.info("{}", recordsOfUpdates);
}
}
这里需要注意,我们创建Bean时使用了带有参数的构造方法(且不含ID),那么还需要在Contact类中声明出无参数的构造方法,也就是在Contact类中添加如下两个构造方法: public Contact() {
super();
}
public Contact(String name, String gender, String mobile, String address) {
super();
this.name = name;
this.gender = gender;
this.mobile = mobile;
this.address = address;
}
运行测试程序,我们会得到如下输出: 可以看出update语句的返回值是1,也就是说我们成功地更新了一行数据,打开数据表,我们可以看到这条数据已经成功添加进去了:
下面来看第二种参数设置方式,就是使用我们之前的ParameterMap方式。使用Bean是ORM特性的体现,而如果在一个事务中,需要向多个表中插入数据,显然使用Bean的方式存在不足,因为中间表可能没有对应的Bean存在,而且构造Bean是消耗系统资源的,特别是在批量操作时,是不建议使用Bean的方式的。那么我们可以使用ParameterMap的方式来统一设置传入参数。
修改SQL语句:
<insert id="insertContact" parameterClass="parameterMap">
insert into
contact(NAME,GENDER,MOBILE,ADDRESS)
values
(#NAME:VARCHAR#,#GENDER:VARCHAR#,#MOBILE:VARCHAR#,#ADDRESS:VARCHAR#)
</insert>
之后在DAO中重载一个insertContact方法: package org.ourpioneer.contact.dao;
import java.util.Map;
import org.ourpioneer.contact.bean.Contact;
public interface ContactDAO {
public Contact getContactById(Map<Object, Object> parameterMap);
public int insertContact(Contact contact);
public int insertContact(Map<Object, Object> parameterMap);
}
然后编写实现代码:public int insertContact(Map<Object, Object> parameterMap) {
return getSqlMapClientTemplate().update("insertContact", parameterMap);
}
和之前的完全类似,只是参数不同,就是重载了一个方法而已。下面是Service方法: public int insertContact(String name, String gender, String mobile,
String address) {
Map<Object, Object> parameterMap = new ParameterMap("NAME", name,
"GENDER", gender, "MOBILE", mobile, "ADDRESS", address);
return getContactDAO().insertContact(parameterMap);
}
这里我们将传入的四个参数重新构造成一个ParameterMap类型的变量,为什么要在Service中完成这步操作,就是出于事务捆绑的操作。在Service层我们将操作视为一个具体的业务,那么它可能向下要调用多个DAO来向不同数据表进行操作,那么在这层构造数据调用各自的DAO完成SQL操作是最合理的,所以我们将数据的构造操作放在了Service层来进行,之后重新改写测试代码: package org.ourpioneer.contact;
import org.ourpioneer.contact.service.ContactService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans.xml");
ContactService contactService = (ContactService) context
.getBean("contactService");
// Contact contact = contactService.getContactById(1);
// Contact contact = new Contact("Tom", "male", "15940990001",
// "Dalian");
int recordsOfUpdates = contactService.insertContact("Tom", "male","15940990001", "Dalian");
logger.info("{}", recordsOfUpdates);
}
}
我们仍然得到了更新一行数据的结果,可以看到数据库中就又多了一条数据。 下面是数据更新操作,也就是SQL的update操作,还是先来编写SQL语句:
<update id="updateContactById" parameterClass="contact">
update contact
set NAME=#name:VARCHAR#,GENDER=#gender:VARCHAR#,MOBILE=#mobile:VARCHAR#,ADDRESS=#address:VARCHAR#
where ID=#id:LONG#
</update>
可以看出,这里也是使用Bean的方式来进行更新,那么DAO代码如下: public int updateContactById(Contact contact) {
return getSqlMapClientTemplate().update("updateContactById", contact);
}
然后是Service代码: public int updateContact(Contact contact) {
return getContactDAO().updateContactById(contact);
}
而测试时,我们是先将一条记录给查出来,之后更改几个属性,再给更新数据,那么测试代码为: public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans.xml");
ContactService contactService = (ContactService) context
.getBean("contactService");
Contact contact = contactService.getContactById(2);
System.out.println(contact);
contact.setMobile("15900000001");
contact.setAddress("Beijing");
int recordsOfUpdates = contactService.updateContact(contact);
logger.info("{}", recordsOfUpdates);
}
运行测试代码,我们得到如下结果: 之后查看数据库,发现ID为2的记录已经更改了:
和插入操作一样,我们还可以使用ParameterMap的方式来实现更新,应用场景也是和前面类似的。这里就不再给出演示了,直接看最后一个操作,就是删除操作。删除应该是最简单的一种操作了,我们编写一个SQL:
<delete id="deleteContactById" parameterClass="parameterMap">
delete from contact where ID=#ID:LONG#
</delete>
我们使用的是parameterMap作为传入的参数类型,那么DAO代码如下: public int deleteContactById(Map<Object, Object> parameterMap) {
return getSqlMapClientTemplate().update("deleteContactById", parameterMap);
}
在Service中,我们构造出parameerMap对象: public int deleteContactById(long id) {
Map<Object, Object> parameterMap = new ParameterMap("ID", id);
return getContactDAO().deleteContactById(parameterMap);
}
下面是测试代码,我们直接指定要删除的记录ID即可: public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"beans.xml");
ContactService contactService = (ContactService) context
.getBean("contactService");
int recordsOfUpdates = contactService.deleteContactById(3);
logger.info("{}", recordsOfUpdates);
}
}
运行测试代码,我们可以看到成功更新了一条记录,也就是将第三条记录删除,那么再看看数据库,也就只有两条记录了。
本部分内容也就全部介绍完了。
本部分内容也就全部介绍完了。
以上内容是否对您有帮助:
更多建议: