本文承接我的另一篇博文:Spring+Jersey+Hibernate+MySQL+HTML實現用戶信息增刪改查案例(附Jersey單元測試),主要更改內容如下:
如果讀者想詳細查看Spring整合Jersey與前端交互可以點擊上述連接。本文主要介紹以上三處修改內容,並且使用Jersey Test測試整合結果正確性。博文最後提供源碼下載。至於JPA的介紹也不是本文的重點,若讀者有不明白的地方可以查看其它文檔。
- Spring配置文件applicationContext中原先使用的是Hibernate,現在改爲Hibernate對JPA的支持;
- 增加了C3P0連接池;
- 修改了Dao操作實現,改爲Spring接管的JPA實現。
一、添加pom依賴
因爲整個項目構建是基於Maven的,所以在使用JPA和C3P0連接池之前,必須先添加相應的依賴JAR包,具體如下:
<!-- hibernate對JPA支持 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.11.Final</version>
</dependency>
<!-- hibernate C3P0連接池包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>4.3.11.Final</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2.1</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.3.4</version>
</dependency>
目前比較成熟的 JPA 框架主要包括 Jboss 的 Hibernate EntityManager、Oracle 捐獻給 Eclipse 社區的 EclipseLink、Apache 的 OpenJPA 等,如上所示,本文使用的是Hibernate的JPA支持。
二、修改applicationContext.xml
正如Spring接管Hibernate一樣,JAP也可以由Spring接管,只需要在applicationContext.xml文件做相應的配置即可,用戶便無需關注諸如事務、安全等非業務邏輯的處理及實現。如下爲修改後的Spring配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 註解支持 -->
<context:annotation-config />
<!-- 啓動組件掃描 -->
<context:component-scan
base-package="com.spring.jersy.jpa.hibernate.resource,com.spring.jersy.jpa.hibernate.dao,com.spring.jersy.jpa.hibernate.service" />
<!-- JDBC屬性文件位置 -->
<context:property-placeholder
location="classpath:com/spring/jersy/jpa/hibernate/config/jdbc.properties" />
<!-- 數據庫連接 -->
<!-- 如果需要配置連接池org.apache.commons.dbcp.BasicDataSource則不符合 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- hibernate.c3p0.min_size: 數據庫連接池的最小連接數 -->
<property name="minPoolSize" value="${jdbc.minPoolSize}" />
<!-- hibernate.c3p0.max_size: 數據庫連接池的最大連接數 -->
<property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
<!-- 指定連接池的初始化連接數 取值應在minPoolSize 與 maxPoolSize 之間.Default:3-->
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
<!-- 緩存 Statement 對象的數量 -->
<property name="maxStatements" value="${jdbc.maxStatements}" />
<!-- 數據庫連接池中連接對象在多長時間沒有使用過後,就應該被銷燬 -->
<property name="maxIdleTime" value="${jdbc.maxIdleTime}" />
<!-- 表示連接池檢測線程多長時間檢測一次池內的所有鏈接對象是否超時 -->
<property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}" />
<!-- 當數據庫連接池中的連接耗盡時, 同一時刻獲取多少個數據庫連接 -->
<property name="acquireIncrement" value="${jdbc.acquireIncrement}" />
</bean>
<!-- Hibernate對Jpa的實現 -->
<bean id="hibernateJpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
<!-- Jpa 事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- 定義實體管理器工廠 Jpa配置 LocalContainerEntityManagerFactoryBean這個選項Spring扮演了容器的角色。完全掌管JPA -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 指定數據源 -->
<property name="dataSource" ref="dataSource" />
<!-- 指定Jpa持久化實現廠商類,這裏以Hibernate爲例 -->
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
<!-- 指定Entity實體類包路徑 -->
<property name="packagesToScan">
<array>
<value>com.spring.jersy.jpa.hibernate.model</value>
</array>
</property>
<!-- 指定JPA屬性;如Hibernate中指定是否顯示SQL的是否顯示、方言等 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
<!-- 配置事務特性,配置add,delete,update開始的方法,事務傳播特性爲required -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置那些類的方法進行事務管理,當前<span style="font-family: Arial, Helvetica, sans-serif;">com.spring.jersy.jpa.hibernate.service</span>包中的子包, 類中所有方法需要,還需要參考tx:advice的設置 -->
<aop:config>
<aop:pointcut id="allManagerMethod"
expression="execution(* com.spring.jersy.jpa.hibernate.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="allManagerMethod" />
</aop:config>
</beans>
jdbc.properties內容如下:
#MySQL jdbc
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root
#c3p0
jdbc.minPoolSize=5
jdbc.maxPoolSize=20
jdbc.initialPoolSize=15
jdbc.maxStatements=50
jdbc.maxIdleTime=300
jdbc.idleConnectionTestPeriod=60
jdbc.acquireIncrement=2
三、修改Dao實現
之前使用Hibernate作爲持久層框架時,Spring提供了HibernateTemplate對數據庫的CRUD等各種操作的實現。現在我們使用JPA操作時,Spring在配置文件中實現了EntityManagerFactory的注入實現,但是並沒有提供EntityManager的實現。如果每次都要使用工廠類生成實體管理器,則非常不利於開發。JPA提供了@PersistenceContext註解才解決這個問題。如下:
package com.spring.jersy.jpa.hibernate.dao;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.stereotype.Repository;
import com.spring.jersy.jpa.hibernate.model.User;
@Repository(value = "userJpaDao")
public class UserJpaDao {
//通過該註解,在類中便無需EntityManagerFactory創建EntityManager了。
@PersistenceContext
private EntityManager entityManager;
// 根據用戶名和密碼查詢
public List<User> findByNameAndPassword(User user) {
//如果sql語句中 select 有部分字段或者全部字段,返回的都是對象數組
String jql = "select u " +
" from User u where u.username=:username and u.password = :password";
Query query = entityManager.createQuery(jql);
query.setParameter("username", user.getUsername());
query.setParameter("password", user.getPassword());
@SuppressWarnings("unchecked")
List<User> listUsers = (List<User>) query.getResultList();
return listUsers;
}
//根據姓名查詢
@SuppressWarnings("unchecked")
public List<User> findUserByName(String name) {
List<User> userList = new ArrayList<User>();
String jql = "from User u where u.username like:name";
Query query = entityManager.createQuery(jql);
query.setParameter("name", name);
userList = (List<User>) query.getSingleResult();
return userList;
}
//添加用戶
public boolean addUser(User user) {
try {
entityManager.persist(user);
} catch (Exception e) {
return false;
}
return true;
}
//刪除用戶
public boolean deleteUser(Integer id) {
try {
User user = entityManager.find(User.class, id);
if(user != null){
entityManager.remove(user);
return true;
}else {
return false;
}
} catch (Exception e) {
return false;
}
}
//修改用戶
public boolean updateUser(User user){
try {
String jql = "update User u set u.username = :name, u.password = :pwd, " +
"u.address = :address, u.tel =:tel where u.id = :id";
Query query = entityManager.createQuery(jql);
query.setParameter("name", user.getUsername())
.setParameter("pwd", user.getPassword())
.setParameter("address", user.getAddress())
.setParameter("tel", user.getTel())
.setParameter("id", user.getId());
if(query.executeUpdate() == 1){
return true;
}else {
return false;
}
} catch (Exception e) {
return false;
}
}
}
四、業務層實現
業務層實現不需要太多修改,只需引用JPA Dao實現即可。具體如下:
package com.spring.jersy.jpa.hibernate.service;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.spring.jersy.jpa.hibernate.dao.UserJpaDao;
import com.spring.jersy.jpa.hibernate.model.User;
@Service(value="userService")
public class UserService {
@Resource(name="userJpaDao")
private UserJpaDao userJpaDao;
public User findUserByNameAndPassword (User user) {
List<User> listUsers = userJpaDao.findByNameAndPassword(user);
if(listUsers.size() > 0) {
return listUsers.get(0);
}
return null;
}
public User findUserByName (String name) {
List<User> listUsers = userJpaDao.findUserByName(name);
if(listUsers.size() > 0) {
return listUsers.get(0);
}
return null;
}
public boolean deleteUser(Integer id) {
return userJpaDao.deleteUser(id);
}
public boolean addUser(User user) {
return userJpaDao.addUser(user);
}
public boolean updateUser(User user){
return userJpaDao.updateUser(user);
}
public UserJpaDao getUserJpaDao() {
return userJpaDao;
}
public void setUserJpaDao(UserJpaDao userJpaDao) {
this.userJpaDao = userJpaDao;
}
}
五、資源類
資源類實現主要是Jersey,本文因爲上接Spring+Jersey+Hibernate+MySQL+HTML實現用戶信息增刪改查案例(附Jersey單元測試)文章,此處Jersey資源類不作任何修改,具體如下:
package com.spring.jersy.hibernate.resource;
import javax.annotation.Resource;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import net.sf.json.JSONObject;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.spring.jersy.hibernate.model.User;
import com.spring.jersy.hibernate.service.UserService;
/**
* Spring 使用Component註解Jersey資源類
* Path:相當於Spring MVC中的RequestMapping,用於HTTP URL請求
* Scope:表示Spring的單例模式或者原型模式prototype
*/
@Component
@Path("/user")
@Scope("prototype")
public class UserResource {
@Resource(name = "userService")
private UserService userService;
private String message;
/**
* GET:表示Jersey rest查詢請求
* Path:此處完整路徑需接類上面的Path,如:user/exist/xx/1233,纔會跳轉到該方法處理
* Consumes:表示前端請求數據格式
* Produces:表示返回值數據格式
* PathParam:用於註解參數變量,與URL中對應的變量名對應,達到傳遞的作用
*/
@GET
@Path("/exist/{username}/{password}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
//查詢
public String isExist(@PathParam("username") String username,
@PathParam("password") String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
User result = userService.findUserByNameAndPassword(user);
/**
* Spring+Jersey框架,不會主動幫助我們將傳遞的對象轉換成JSON數據格式,
* 需使用JSON lib類中的JSONObject或者JSONArray轉換才能夠傳遞,否則前端
* 會報302錯誤。
*/
JSONObject jsonUser = JSONObject.fromObject(result);
return jsonUser.toString();
}
@POST
@Path("/addUser")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
//添加
public String addUser(User user) {
boolean flag = userService.addUser(user);
if (flag) {
message = "success";
} else {
message = "fail";
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("message", message);
return jsonObject.toString();
}
@DELETE
@Path("/deleteUser/{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
//刪除
public String deleteUser(@PathParam("id") Integer id) {
boolean flag = userService.deleteUser(id);
if (flag) {
message = "success";
} else {
message = "fail";
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("message", message);
return jsonObject.toString();
}
@PUT
@Path("/updateUser/{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
//修改
public String updateUser(@PathParam("id") Integer id, User user) {
user.setId(id);
boolean flag = userService.updateUser(user);
if (flag) {
message = "success";
} else {
message = "fail";
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("message", message);
return jsonObject.toString();
}
public void setUserService(UserService userService) {
this.userService = userService;
}
}
六、單元測試
此處使用的單元測試是Jersey Test,測試類內容如下:
package com.spring.jersy.hibernate.test;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.request.RequestContextListener;
import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
import com.sun.jersey.test.framework.JerseyTest;
import com.sun.jersey.test.framework.WebAppDescriptor;
/**
* 主要加載web.xml中的配置信息
* @author Administrator
*
*/
public abstract class BaseJerseyServiceTest extends JerseyTest {
@Override
protected WebAppDescriptor configure() {
return new WebAppDescriptor.Builder("com.spring.jersy.jpa.hibernate.resource")
.contextParam( "contextConfigLocation", "classpath:com/spring/jersy/hibernate/config/applicationContext.xml")
//.servletClass(SpringServlet.class)
.filterClass(SpringServlet.class)
.initParam("com.sun.jersey.config.feature.Redirect", "true")
.initParam("com.sun.jersey.config.feature.FilterForwardOn404", "true")
.initParam("com.sun.jersey.config.property.WebPageContentRegex", "/(images|css|jsp)/.*")
.initParam("com.sun.jersey.api.json.POJOMappingFeature", "true")
.initParam("com.sun.jersey.config.property.packages", "com.spring.jersy.jpa.hibernate.resource")
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class)
.build();
}
}
package com.spring.jersy.jpa.hibernate.test;
import javax.ws.rs.core.MediaType;
import org.junit.Before;
import org.junit.Test;
import com.spring.jersy.jpa.hibernate.model.User;
import com.sun.jersey.api.client.WebResource;
public class UserResourceTest extends BaseJerseyServiceTest {
private WebResource webResource;
@Before
public void setUp() throws Exception {
webResource = resource();
// webResource.accept("application/json");
}
@Test
public void testIsExist() {
String response = webResource.path("/user/exist/xx/1233").get(
String.class);
System.out.println(response);
}
@Test
public void testAddUser() {
User user = new User();
user.setUsername("wwwx");
user.setPassword("pwd");
user.setAddress("江蘇鹽城");
user.setTel("12333");
String response = webResource.path("/user/addUser")
.entity(user, "application/json").post(String.class, user);
System.out.println(response.toString());
}
@Test
public void testDeleteUser() {
String response = webResource.path("/user/deleteUser/59").delete(
String.class);
System.out.println(response.toString());
}
@Test
public void testUpdateUser() {
User user = new User();
user.setUsername("王三兒");
user.setPassword("wangsaner");
String response = webResource.path("/user/updateUser/64")
.entity(user, MediaType.APPLICATION_JSON).put(String.class);
System.out.println(response.toString());
}
}
七、測試結果如下:
Hibernate:
insert
into
test.user
(username, password, address, tel)
values
(?, ?, ?, ?)
{"message":"success"}
Hibernate:
select
user0_.id as id1_2_0_,
user0_.username as username2_2_0_,
user0_.password as password3_2_0_,
user0_.address as address4_2_0_,
user0_.tel as tel5_2_0_
from
test.user user0_
where
user0_.id=?
{"message":"fail"}
Hibernate:
update
test.user
set
username=?,
password=?,
address=?,
tel=?
where
id=?
Hibernate:
select
user0_.id as id1_2_,
user0_.username as username2_2_,
user0_.password as password3_2_,
user0_.address as address4_2_,
user0_.tel as tel5_2_
from
test.user user0_
where
user0_.username=?
and user0_.password=?
{"address":"ss","id":48,"password":"1233","tel":"1232333","username":"xx"}
附1:異常
將項目部署到服務器上運行,在Web調試過程中,所有調試一切正常,但是在運行單元測試的時候報如下異常錯誤:
Caused by: java.lang.NoSuchMethodError: javax.persistence.Table.indexes()[Ljavax/persistence/Index;
at org.hibernate.cfg.annotations.EntityBinder.processComplementaryTableDefinitions(EntityBinder.java:973)
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:824)
at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3845)
at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3799)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1412)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:425)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:849)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:343)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1625)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1562)
... 45 more
解決辦法:具體原因是因爲項目中存在其他版本的JPA實現,導致版本衝突,在調用javax.persistence.*時衝突,如Eclipse link,所以需要在pom.xml中去掉Eclipse 對JPA的支持。
<!-- 增加Eclipse JPA Provider支持 -->
<!-- <dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>2.6.0</version>
</dependency>
-->
附2:源碼下載