问题背景
在spring data jpa的框架设计中,实体类(entity)与数据表一一对应,默认对实体操作时即是对整条数据库记录操作,因此在jpa的保存操作中,保存一个实体即是更新数据库记录的所有字段。基于这种设计,在实际使用中有如下不便利的地方:
1. 在实际业务中,业务数据会有逐步完善的情况,即在不同的阶段,会由不同的人员录入不同的字段信息,最终形成一个完整的业务数据记录。在这种情况下,每个阶段需要补充的信息(即页面中填写的信息)仅为一个信息片段,此时如果不对实体信息进行补全,在保存时即会出现信息丢失的情况。
2. 在实际业务中,核心基础数据会有需要进行拓展的情况,即要增加字段补充信息,而核心数据引用范围一般比较广泛,在jpa的原始设计中,对核心数据的拓展会导致大范围的功能调整。
解决方案
基于以上背景,考虑在spring data jpa的基础上构造公用的合并更新类,在更新实体信息前首先取到完整的实体数据,再基于请求接收的参数对实体数据进行合并,最终将合并后的实体进行保存。
其中:
1. 合并中需保留值的字段由controller中接收到的参数决定。
2. 合并操作应在保存的前一步执行,此时实体类中相关信息已填充完毕。
3. 实体类中,与数据库的对应关系由注解定义,因此使用反射方式,可以确定实体类中对应到数据库记录主键的字段,并获取到主键的值,基于这些信息,可获取到任意实体的数据库数据。
基于上述实际情况,代码中采用如下设计:
1. 使用拦截器拦截请求并将参数名称存储到请求的ThreadLocal中,此时,当controller/service/dao的代码在同一个线程中处理时,在任一层次的任意方法中,都可以直接获取到拦截器中存储的信息,无需对历史业务代码进行调整。
拦截器代码如下:
package com.hhh.sgzl.system.interceptors; import com.hhh.base.model.PageParam; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Enumeration; public class PageParamInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Enumeration<String> requestParamNames = request.getParameterNames(); //清理上次线程残留 PageParam.clear(); if (requestParamNames != null) { String paramName; String[] paramNameParts; while (requestParamNames.hasMoreElements()) { paramName = requestParamNames.nextElement(); if (paramName.indexOf(".") != -1) { //表单参数 paramNameParts = paramName.split("."); PageParam.add(paramNameParts[0], paramNameParts[1]); } else { //非表单参数 PageParam.add(paramName); } } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
线程数据存储以单独的类处理,此类的代码如下:
package com.hhh.base.model; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 存储页面参数,用于后续的实体部分更新 */ public final class PageParam { private static final ThreadLocal<Map<String, List<String>>> PAGE_PARAM_NAMES = new ThreadLocal<>(); //存储页面参数名 private static final String DEFALT_KEY = "_head"; //默认key值,用于存储表体外的其他参数名 /** * 内容依附于线程,无需实例化 */ private PageParam() { } /** * 获取参数名的List * @param key 两种取值,表头类型参数名key固定为_head,表单类型参数名为表单对应的Bean的类名 * @return 参数名的List */ public static List<String> get(String key) { Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get(); if (nameMap != null) { return nameMap.get(key); } else { return null; } } /** * 获取列表外的参数名(单据的表单头) * @return 列表外的参数名 */ public static List<String> get() { Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get(); if (nameMap != null) { return nameMap.get(DEFALT_KEY); } else { return null; } } /** * 记录参数名 * @param key 参数名对应的key * @param value 参数名 */ public static void add(String key, String value) { Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get(); if (nameMap == null) { nameMap = new HashMap<>(); PAGE_PARAM_NAMES.set(nameMap); } if (!nameMap.containsKey(key)) { nameMap.put(key, new ArrayList<>()); } nameMap.get(key).add(value); } /** * 记录参数名 * @param value 参数名 */ public static void add(String value) { add(DEFALT_KEY, value); } } /** * 清理上次线程参数残留 */ public static void clear() { Map<String, List<String>> nameMap = PAGE_PARAM_NAMES.get(); if (nameMap != null) { nameMap.clear(); } } }
更新前合并数据库数据的公用dao层类代码如下:
package com.hhh.sgzl.system.dao; import com.hhh.base.model.PageParam; import com.hhh.exceptions.ParticalUpdateException; import org.apache.commons.beanutils.PropertyUtils; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Repository; import org.springframework.util.Assert; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * 基于页面字段决定所需要更新字段 */ @Repository public class ParticalUpdateDao { @PersistenceContext(unitName = "mysqlUnit") private EntityManager entityManager; /** * 根据页面字段设置来部分更新实体 * @param entity 实体对象 * @param <E> 实体类对应的类定义 */ public <E> void save(E entity) { Assert.notNull(entity, "保存的实体不能为null"); String pkFieldName = this.getPkFieldName(entity); Assert.notNull(pkFieldName, "无法获取主键字段"); try { String pkFieldVal = (String) PropertyUtils.getProperty(entity, pkFieldName); if (pkFieldVal == null || pkFieldVal.trim().equals("")) { //新增 PropertyUtils.setSimpleProperty(entity, pkFieldName, null); entityManager.persist(entity); } else { //修改,需对比同步数据 List<String> pageFields = this.getPageFields(entity); if (pageFields == null || pageFields.isEmpty()) { throw new ParticalUpdateException("实体无法和页面参数建立关联"); } E dataBaseRecord = entityManager.find((Class<E>) entity.getClass(), pkFieldVal); String[] ignoreFields = new String[pageFields.size()]; BeanUtils.copyProperties(dataBaseRecord, entity, pageFields.toArray(ignoreFields)); entityManager.merge(entity); } } catch (Exception e) { throw new ParticalUpdateException(e.getMessage()); } } /** * 获取页面上有的实体类字段 * @param entity 实体类对象 * @param <E> 实体类对应的类定义 * @return 页面上有的实体类字段 */ private <E> List<String> getPageFields(E entity) { Class entityClass = entity.getClass(); List<String> pageParamNames = this.getPageNames(entityClass); if (pageParamNames == null) { throw new ParticalUpdateException("实体无法和页面参数建立关联"); } Field[] fields = entityClass.getDeclaredFields(); List<String> fieldNames = new ArrayList<>(); for (Field field : fields) { fieldNames.add(field.getName()); } List<String> pageFields = new ArrayList<>(); for (String paraName : pageParamNames) { if (fieldNames.contains(paraName)) { pageFields.add(paraName); } } return pageFields; } /** * 获取页面上关联的参数名 * @param entityClass 实体类对应的类定义 * @return 页面上关联的参数名 */ private List<String> getPageNames(Class entityClass) { String entityClassName = entityClass.getSimpleName(); List<String> pageParaNames = PageParam.get(entityClassName); if (pageParaNames == null) { pageParaNames = PageParam.get(entityClassName + "Bean"); } if (pageParaNames == null) { pageParaNames = PageParam.get(); } return pageParaNames; } /** * 获取主键字段名 * @param entity 实体对象 * @param <E> 实体类对应的类定义 * @return 主键字段名 */ private <E> String getPkFieldName(E entity) { Class entityClass = entity.getClass(); Field[] fields = entityClass.getDeclaredFields(); String pkFieldName = null; if (fields != null) { for (Field field : fields) { if (this.isPkField(field)) { pkFieldName = field.getName(); break; } } } return pkFieldName; } /** * 判断是否为实体的主键字段 * @param field bean的字段信息 * @return 若为实体的主键字段,则返回true */ private boolean isPkField(Field field) { Annotation[] fieldAnnotations = field.getDeclaredAnnotations(); boolean isPkfield = false; if (fieldAnnotations != null) { for (Annotation fieldAnnotation : fieldAnnotations) { if (fieldAnnotation.annotationType().getName().equals("javax.persistence.Id")) { isPkfield = true; break; } } } return isPkfield; } }
不足
1. 公用类中使用了大量的反射,以达到对所有实体类均可生效的效果,效率较正常代码低下。
2. 为避免修改历史业务代码,使用了ThreadLocal传递参数,因此此方法仅可以在单应用服务器的部署场景中使用,不适用于分布式的部署方式。
相关推荐
产生原因:因为使用 jpa 自动创建表的时候,采用的是TreeMap的,我们要变成LinkedHashMap 的结构。 解决方案:在项目下java文件夹下创建包名为: org.hibernate.cfg ,创建类: PropertyContainer [包名,类名...
2. **Entity Management**:Spring Data JPA 提供了对实体(Entity)的管理,包括实体的创建、更新、删除等操作。开发者可以通过 `EntityManager` 和 `EntityManagerFactory` 进行这些操作,但 Spring Data JPA 提供...
例如,Spring Data JPA支持自动化的查询生成,只需定义Repository接口,无需编写任何实现代码,就可以执行CRUD(创建、读取、更新、删除)操作。此外,它还支持复杂的查询方法命名,如findByXXX,根据方法名自动构建...
在Spring JPA中,你可以通过定义Repository接口来实现对数据库的操作,而Spring会自动帮你完成数据访问的大部分工作。 1. **什么是JPA?** Java Persistence API (JPA) 是Java平台上的一个标准,它定义了如何在Java...
Spring Data JPA API。 Spring Data JPA 开发文档。 官网 Spring Data JPA API。
`@DynamicUpdate`是一个实体类级别的注解,当被标记在实体类上时,表示在执行更新操作时,只会更新那些值已经改变的属性,而不是默认的全字段更新。 在描述中提到的博文链接虽然没有给出详细内容,但通常这样的博文...
本文将详细探讨在使用JPA进行实体映射时如何处理时间字段,并提供具体的代码示例和实践建议。 #### 一、时间字段的格式化 在Java中,时间通常由`java.util.Date`类或其子类来表示。但在实际应用中,我们常常需要将...
只需要在接口上定义方法名,Spring Data JPA就能自动生成对应的SQL语句。例如,`findAll()`会执行SELECT ALL查询,`findByUsername(String username)`将生成一个根据用户名查找的查询。此外,`Pageable`接口可用于...
在IT行业中,构建高效、可维护的Web应用是至关重要的,而"Maven+SpringMVC+SpringJPA+Hibernate"的组合就是一个常见的解决方案。这个组合提供了全面的开发工具和技术,帮助开发者快速构建基于Java的Web应用程序。...
springboot jpa 自动生成实体类的 文件 可以拿走直接用 Generate POJOs.groovy
Spring JPA通过提供Repository接口、实体管理、事务处理等特性,极大地提升了开发效率。 1. **Repository接口**:Spring JPA的核心在于Repository接口,这是一个接口模板,允许用户自定义数据操作方法。例如,你...
一、单表查询的一个字段、一条数据 @Query(value = select username from user,nativeQuery = true) // 注意返回值用String类型接收,也可以使用Object String findOneUserName(); 二、单表查询的一个字段、多条...
在使用 Spring Data Jpa 进行数据更新时,经常会遇到一个问题,即当属性值为 Null 时,也会被更新,这将导致原本没有更新的属性值全部变为 Null。这个问题的解决方案是使用 @DynamicUpdate 注解,但是这种方法并不好...
例如,`@OneToMany`和`@ManyToOne`用于表示一对多或多对一的关系,级联操作如`CascadeType.PERSIST`、`CascadeType.MERGE`等可以确保当一个实体被保存或更新时,与其关联的实体也会相应更新。这在处理关联数据时非常...
此外,还需要配置Spring的数据源、事务管理器以及JPA的实体扫描路径。 3. **JPA配置**:JPA的配置主要涉及`persistence.xml`文件,它定义了持久化单元,包括数据源、JPA供应商(如Hibernate)、实体类的包名以及...
同时,为实体类提供Repository接口,Spring Data JPA会自动生成对应的方法。 在Spring MVC的Controller中,你可以通过@Autowired注解注入Repository实例,然后调用其方法来执行数据库操作。例如,使用save()方法...
在Spring中,我们通常使用Spring Data JPA扩展,它提供了更多的便利功能,如自动查询生成、repository抽象等。要开始使用Spring JPA,我们需要以下步骤: 1. **配置JPA**:在`application.properties`或`...
Spring Data JPA是Spring框架的一个模块,用于简化Java Persistence API(JPA)的使用,它提供了与数据库交互的高级抽象。在Spring应用中整合Spring Data JPA,可以极大地提高开发效率,减少大量的DAO层代码。下面...
在本项目中,我们探索的是一个基于2017年技术栈的Java Web应用程序实例,主要涉及Spring Data JPA、Spring 4.2、Spring MVC和Hibernate 4.3。这些技术是Java开发中的核心组件,尤其在企业级应用开发中广泛使用。下面...