首页 > 代码库 > 快速搭建springmvc+spring data jpa工程
快速搭建springmvc+spring data jpa工程
一、前言
这里简单讲述一下如何快速使用springmvc和spring data jpa搭建后台开发工程,并提供了一个简单的demo作为参考。
二、创建maven工程
http://www.cnblogs.com/hujunzheng/p/5450255.html
三、配置文件说明
1.application.properties
jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/springdata?useUnicode=true&characterEncoding=utf-8jdbc.username=rootjdbc.password=123456jdbc.pool.initialSize=5jdbc.pool.maxActive=300jdbc.pool.maxIdle=10jdbc.pool.minIdle=8jdbc.pool.maxWait=60000jdbc.pool.minEvictableIdleTimeMillis=6000jdbc.pool.removeAbandoned=truejdbc.pool.removeAbandonedTimeout=120jdbc.pool.logAbandoned=truejdbc.pool.testOnBorrow=truejdbc.pool.testWhileIdle=truejdbc.pool.validationQuery=select 1 from dualjdbc.pool.validationQueryTimeout=1jdbc.pool.timeBetweenEvictionRunsMillis=30000jdbc.pool.numTestsPerEvictionRun=300
配置数据库连接的常用属性
2.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd" default-lazy-init="true"> <description>Spring公共配置</description> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>/WEB-INF/config/application.properties</value> </list> </property> </bean> <bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>/WEB-INF/config/application.properties</value> </list> </property> </bean> <!-- 使用annotation 自动注册bean, 并保证@Required、@Autowired的属性被注入 --> <context:component-scan base-package="com.hjz"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan></beans>
配置configProperties(可以在xml文件中读取application.properties中属性),当然换成<context:property-placeholder location="application.properties"/>更简洁一些。还有就是使用annotation 自动注册bean, 并保证@Required、@Autowired的属性被注入,这里排除了Controller注解类的扫描。
3.spring mvc配置文件
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.hjz" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8" /> <property name="writeAcceptCharset" value="false" /> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="prettyPrint" value="true" /> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <!-- 静态资源访问 --> <mvc:resources location="/WEB-INF/img/" mapping="/img/**"/> <mvc:resources location="/WEB-INF/js/" mapping="/js/**"/> <mvc:resources location="/WEB-INF/css/" mapping="/css/**"/> <mvc:default-servlet-handler /></beans>
配置对Controller注解类的扫描,消息转换器(针对String和Json的解析),视图解析器(这里只配置了jsp的视图解析器),静态资源的访问映射。
4.spring data jpa配置文件
<?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:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd" default-lazy-init="true"> <!-- 数据源配置, 使用Tomcat JDBC连接池 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!--initialSize: 初始化连接 --> <property name="initialSize" value="${jdbc.pool.initialSize}" /> <!--maxActive: 最大连接数量 --> <property name="maxActive" value="${jdbc.pool.maxActive}" /> <!--minIdle: 最小空闲连接 --> <property name="minIdle" value="${jdbc.pool.minIdle}" /> <!--maxIdle: 最大空闲连接 --> <property name="maxIdle" value="${jdbc.pool.maxIdle}" /> <!--maxWait: 超时等待时间以毫秒为单位 --> <property name="maxWait" value="${jdbc.pool.maxWait}" /> <!-- 连接被泄露时是否打印 --> <property name="logAbandoned" value="${jdbc.pool.logAbandoned}" /> <!--removeAbandoned: 是否自动回收超时连接 --> <property name="removeAbandoned" value="${jdbc.pool.removeAbandoned}" /> <!--removeAbandonedTimeout: 超时时间(以秒数为单位) --> <property name="removeAbandonedTimeout" value="${jdbc.pool.removeAbandonedTimeout}" /> <!-- 连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.pool.minEvictableIdleTimeMillis}" /> <property name="testOnBorrow" value="${jdbc.pool.testOnBorrow}" /> <property name="testWhileIdle" value="${jdbc.pool.testWhileIdle}" /> <property name="validationQuery" value="${jdbc.pool.validationQuery}" /> <property name="validationQueryTimeout" value="${jdbc.pool.validationQueryTimeout}" /> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.pool.timeBetweenEvictionRunsMillis}" /> <property name="numTestsPerEvictionRun" value="${jdbc.pool.numTestsPerEvictionRun}" /> </bean> <!-- Jpa Entity Manager 配置 --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" /> <property name="packagesToScan"> <array> <value>com.hjz</value> </array> </property> <property name="jpaProperties"> <props> <!-- 命名规则 My_NAME->MyName --> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> <prop key="hibernate.show_sql">true</prop> </props> </property> </bean> <!-- Spring Data Jpa配置 --> <jpa:repositories base-package="com.hjz" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory" factory-class="com.hjz.dao.impl.BaseDaoFactoryBean"> </jpa:repositories> <bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="databasePlatform"> <bean factory-method="getDialect" class="org.springside.modules.persistence.Hibernates"> <constructor-arg ref="dataSource" /> </bean> </property> </bean> <!-- Jpa 事务配置 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean></beans>
数据源配置, 使用Tomcat JDBC连接池;Jpa Entity Manager 配置;Spring Data Jpa repository 自定义工厂配置。
5.web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>Archetype Created Web Application</display-name> <welcome-file-list> <welcome-file>/WEB-INF/views/index.html</welcome-file> </welcome-file-list> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/config/applicationContext.xml, /WEB-INF/config/applicationContext-jpa.xml </param-value> </context-param> <context-param> <param-name>spring.profiles.default</param-name> <param-value>production</param-value> </context-param> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>accessControlFilter</filter-name> <filter-class>com.hjz.filter.AccessControlFilter</filter-class> </filter> <filter-mapping> <filter-name>accessControlFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>openEntityManagerInViewFilter</filter-name> <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>openEntityManagerInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>springServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/spring-mvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <session-config> <session-timeout>20</session-timeout> </session-config> <error-page> <exception-type>java.lang.Throwable</exception-type> <location>/WEB-INF/views/error/500.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/WEB-INF/views/error/500.jsp</location> </error-page> <error-page> <error-code>404</error-code> <location>/WEB-INF/views/error/404.jsp</location> </error-page></web-app>
配置welcome-file-list,contextConfigLocation,encodingFilter,accessControlFilter(解决跨域访问),openEntityManagerInViewFilter(将一个JPAsession与一次完整的请求过程对应的线程相绑定,session在整个view层结束后才关闭。),Spring ContextLoaderListener,Spring MVC DispatcherServlet。
四、spring data jpa自定义repository工厂
1.基础实体类
package com.hjz.entity;import org.hibernate.annotations.GenericGenerator;import javax.persistence.Column;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.MappedSuperclass;import java.io.Serializable;import java.sql.Timestamp;@MappedSuperclasspublic abstract class AbsIdEntity implements Serializable { private static final long serialVersionUID = 7988377299341530426L; public final static int IS_DELETE_YES = 1;// 标记删除 public final static int IS_DELETE_NO = 0;// 未删除,保留的 @Id @GenericGenerator(name="uuid", strategy="uuid") @GeneratedValue(generator="uuid") protected String id; @Column(name = "creationtime") protected Timestamp creationTimestamp = new Timestamp(System.currentTimeMillis()); @Column(name = "lastmodifiedtime") protected Timestamp modificationTimestamp = new Timestamp(System.currentTimeMillis()); @Column(name = "dr") protected int dr;// 是否删除。0:未删除;1:已删除 /** * 主键,对应id字段 */ public String getId() { return id; } public void setId(String id) { this.id = id; } /** * 创建日期,对应ts_insert字段 */ public Timestamp getCreationTimestamp() { return creationTimestamp; } public void setCreationTimestamp(Timestamp creationTimestamp) { this.creationTimestamp = creationTimestamp; } /** * 修改日期,对应ts_update字段 */ public Timestamp getModificationTimestamp() { return modificationTimestamp; } public void setModificationTimestamp(Timestamp modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; } /** * 是否删除,对应dr字段 * @return */ public int getDr() { return dr; } public void setDr(int dr) { this.dr = dr; }}
首先说明,所有的实体类都会继承 基础实体类 AbsIdEntity。
对应的VO类
package com.hjz.vo;import java.io.Serializable;public class PersonVO extends SuperVO implements Serializable{ private static final long serialVersionUID = -4745138914786180462L; private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
2.自定义工厂
package com.hjz.dao.impl;import java.io.Serializable;import javax.persistence.EntityManager;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;import org.springframework.data.jpa.repository.support.SimpleJpaRepository;import org.springframework.data.repository.core.RepositoryInformation;import org.springframework.data.repository.core.RepositoryMetadata;import org.springframework.data.repository.core.support.RepositoryFactorySupport;import com.hjz.entity.AbsIdEntity;public class BaseDaoFactoryBean<R extends JpaRepository<T, Serializable>, T extends AbsIdEntity> extends JpaRepositoryFactoryBean<R, T, Serializable> { @Override protected RepositoryFactorySupport createRepositoryFactory(final EntityManager entityManager) { return new JpaRepositoryFactory(entityManager) { protected SimpleJpaRepository<T, Serializable> getTargetRepository( RepositoryInformation information, EntityManager entityManager) { return new BaseDaoImpl((Class<T>) information.getDomainType(), entityManager); } @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { return BaseDaoImpl.class; } }; }}
工厂中返回的自定义的JpaRepository, 如下。
3.自定义JpaRepository
package com.hjz.dao.impl;import java.io.Serializable;import java.sql.Timestamp;import javax.persistence.EntityManager;import org.springframework.data.jpa.repository.support.JpaEntityInformation;import org.springframework.data.jpa.repository.support.SimpleJpaRepository;import org.springframework.transaction.annotation.Transactional;import com.hjz.dao.BaseDao;import com.hjz.entity.AbsIdEntity;@Transactionalpublic class BaseDaoImpl<T extends AbsIdEntity> extends SimpleJpaRepository<T, Serializable> implements BaseDao<T> { private final EntityManager entityManager; public BaseDaoImpl(Class<T> domainClass, EntityManager entityManager) { super(domainClass, entityManager); this.entityManager = entityManager; } public BaseDaoImpl(JpaEntityInformation<T, Serializable> information, EntityManager entityManager){ super(information, entityManager); this.entityManager = entityManager; } @Override public <S extends T> S save(S entity) { entity.setModificationTimestamp(new Timestamp(System.currentTimeMillis())); return super.save(entity); } //进行逻辑删除 @Override public void delete(T entity) { entity.setDr(1); save(entity); } @Override public void delete(Serializable id) { T entity = findOne(id); entity.setDr(1); this.save(entity); } }
重写了删除方法,这里做逻辑删除。
五、后台开发模式
1.实体类
package com.hjz.entity;import java.io.Serializable;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.Table;@Entity@Table(name = "person")public class PersonEntity extends AbsIdEntity implements Serializable{ private static final long serialVersionUID = -1649223236097252346L; @Column(name = "name") private String name; @Column(name = "age") private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
实体对应的VO
package com.hjz.vo;import java.io.Serializable;public class PersonVO extends SuperVO implements Serializable{ private static final long serialVersionUID = -4745138914786180462L; private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
2.repository类
package com.hjz.dao;import java.util.List;import javax.transaction.Transactional;import org.springframework.data.jpa.repository.Modifying;import org.springframework.data.jpa.repository.Query;import com.hjz.entity.PersonEntity;public interface PersonDao extends BaseDao<PersonEntity>{ @Query(value = "select * from person where dr = 0", nativeQuery = true) List<PersonEntity> findAllPersons(); @Query(value = "select * from person where dr = 0 and id = ?1", nativeQuery = true) PersonEntity findOneById(String id); @Transactional @Modifying @Query(value = "update PersonEntity set dr = 1 where id in (?1)") int deletePersons(List<String> ids);}
3.service类
package com.hjz.service;import java.util.ArrayList;import java.util.List;import javax.persistence.criteria.CriteriaBuilder;import javax.persistence.criteria.CriteriaQuery;import javax.persistence.criteria.Predicate;import javax.persistence.criteria.Root;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.jpa.domain.Specification;import org.springframework.stereotype.Service;import com.hjz.dao.PersonDao;import com.hjz.entity.PersonEntity;import com.hjz.vo.PersonVO;@Servicepublic class PersonService { @Autowired private PersonDao personDao; public List<PersonVO> findAllPersons(){ List<PersonVO> listVO = new ArrayList<>(); List<PersonEntity> listEntity = personDao.findAllPersons(); for(PersonEntity entity : listEntity) { PersonVO vo = new PersonVO(); BeanUtils.copyProperties(entity, vo); listVO.add(vo); } return listVO; } public List<PersonVO> findPageablePersons(Pageable pageable){ List<PersonVO> listVO = new ArrayList<>(); Page<PersonEntity> pageEntity = personDao.findAll(new Specification<PersonEntity>() { @Override public Predicate toPredicate(Root<PersonEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.equal(root.get("dr").as(Integer.class), 0); } }, pageable); for (PersonEntity entity : pageEntity) { PersonVO personVO = new PersonVO(); BeanUtils.copyProperties(entity, personVO); listVO.add(personVO); } return listVO; } public long count() { return personDao.count(new Specification<PersonEntity>() { @Override public Predicate toPredicate(Root<PersonEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.equal(root.get("dr").as(Integer.class), 0); } }); } public PersonVO findOnePerson(String id){ PersonVO vo = new PersonVO(); PersonEntity entity = personDao.findOneById(id); BeanUtils.copyProperties(entity, vo); return vo; } public void deleteOnePerson(String id) { personDao.delete(id); } public PersonVO save(PersonVO vo){ PersonEntity entity = new PersonEntity(); BeanUtils.copyProperties(vo, entity); personDao.save(entity); BeanUtils.copyProperties(entity, vo); return vo; } public void deletePersons(List<String> ids) { personDao.deletePersons(ids); }}
4.controller类
package com.hjz.controller;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.PageRequest;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.servlet.ModelAndView;import com.alibaba.fastjson.JSONObject;import com.hjz.code.ReturnCode;import com.hjz.page.PagableResponse;import com.hjz.service.PersonService;import com.hjz.vo.PersonVO;@Controller@RequestMapping(value = "person")public class PersonController { @Autowired private PersonService personService; @RequestMapping(value = "save", method = RequestMethod.POST) @ResponseBody public JSONObject save(@RequestBody PersonVO personVO){ JSONObject ret = new JSONObject(); try { personService.save(personVO); ret.put("msg", "保存成功!"); ret.put("code", ReturnCode.SUCCESS); } catch (Exception e) { e.printStackTrace(); ret.put("msg", "保存失败!"); ret.put("code", ReturnCode.FAILURE); } return ret; } @RequestMapping(value = "del") @ResponseBody public JSONObject del(@RequestParam(value="http://www.mamicode.com/id") String id){ JSONObject ret = new JSONObject(); try { personService.deleteOnePerson(id); ret.put("msg", "删除成功!"); ret.put("code", ReturnCode.SUCCESS); } catch (Exception e) { e.printStackTrace(); ret.put("msg", "删除失败!"); ret.put("code", ReturnCode.FAILURE); } return ret; } @RequestMapping(value = "delBatch", method = RequestMethod.POST) @ResponseBody public JSONObject delBatch(@RequestParam("ids") List<String> ids){ JSONObject ret = new JSONObject(); try { personService.deletePersons(ids); ret.put("msg", "批量删除成功!"); ret.put("code", ReturnCode.SUCCESS); } catch (Exception e) { e.printStackTrace(); ret.put("msg", "批量删除失败!"); ret.put("code", ReturnCode.FAILURE); } return ret; } @RequestMapping(value = "page") public ModelAndView page(@RequestParam("pageNumber") int pageNumber, @RequestParam("pageSize") int pageSize) { PageRequest pageRequest = new PageRequest(pageNumber-1, pageSize); PagableResponse<PersonVO> response = new PagableResponse<PersonVO>(); response.setPageNumber(pageNumber); response.setPageSize(pageSize); try { List<PersonVO> data =http://www.mamicode.com/ personService.findPageablePersons(pageRequest); long count = personService.count(); response.setList(data); response.setCount(count); response.setCode(ReturnCode.SUCCESS); response.setMsg("获取人员信息成功!"); } catch(Exception e) { e.printStackTrace(); response.setCode(ReturnCode.FAILURE); response.setMsg("获取人员信息失败!"); } return new ModelAndView("person", "page", response); }}
六、前端开发
简单的使用jquery及其一些插件完成了数据的分页(jquery.paginate.js),数据的新增,删除(批量删除)等操作。具体的页面代码请看完整项目。演示效果如下图所示。
七、完整项目
https://github.com/hjzgg/springmvcAndSpringDataJpa
快速搭建springmvc+spring data jpa工程