Краткое описание проблемы
Сбой весенней загрузки. Вызов службы PUT REST завершается с ошибкой стека потока в AuditAwareImpl.
Описание проблемы
У меня есть простое приложение с пружинной загрузкой с двумя сущностями.
Сущность 1: Пользователь | Объект 2: Проект
Пользователь может создать запись объекта проекта (или), может изменить запись объекта проекта (или) может быть руководителем проекта для записи объекта проекта.
Теперь я могу успешно использовать загрузочную систему Sprint - POST / GET-All / Get-One и видеть повторяющиеся записи.
Однако, когда я пытаюсь использовать PUTПри вызове REST для обновления любого атрибута объекта Project я вижу следующее исключение в AuditAwareImpl.
Логика, используемая в службе PUT -
- Получение полезной нагрузки с атрибутами дляupdate.
- Также идентифицируйте обновляемую запись, отправленную через RequestParam - идентификатор.
- Извлеките текущую запись в базе данных для данного идентификатора.
- Обновите атрибуты (обновляемые)из ввода в выбранной записи БД.
- Сохранить.
Ниже приведен снимок используемого кода.
package com.app.mycompany.AgileCenterServices.entities;
import java.util.Date;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.context.annotation.Scope;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
@Entity
@Table(name="Project")
@EntityListeners(AuditingEntityListener.class)
@Scope("session")
public class Project {
@Id
@GenericGenerator(name = "sequence_project_id", strategy = "com.app.mycompany.AgileCenterServices.util.ProjectIdGenerator")
@GeneratedValue(generator = "sequence_project_id")
@Column(unique = true)
private String id;
private String name;
private String description;
private String template;
private String projectKey;
private String type;
private String category;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn (name = "projectLead", referencedColumnName = "username")
private User projectLead;
@Column(nullable = false, name = "active", columnDefinition = "BOOLEAN")
private Boolean active;
@CreatedBy
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn (nullable = false, updatable = false, name = "createdBy", referencedColumnName = "username")
private User createdBy;
@CreatedDate
@Column(nullable = false, updatable = false)
private Date createdDate;
@LastModifiedBy
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn (nullable = false, updatable = false, name = "modifiedBy", referencedColumnName = "username")
private User modifiedBy;
@LastModifiedDate
@Column(nullable = false)
private Date modifiedDate;
public Project() {}
/* Constructor using fields */
/* getters and setters */
}
User.java
package com.app.mycompany.AgileCenterServices.entities;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.JoinColumn;
import org.springframework.context.annotation.Scope;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.GrantedAuthority;
//import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
@Entity
@Table(name="User")
@Scope("session")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@JsonProperty(access = Access.WRITE_ONLY)
private String password;
private String fullname;
private String email;
private Boolean active;
private Boolean superuser;
@JsonProperty(access = Access.WRITE_ONLY)
@OneToMany(mappedBy="projectLead", cascade = CascadeType.PERSIST)
private Set<Project> projectLeadList;
@JsonProperty(access = Access.WRITE_ONLY)
@OneToMany(mappedBy="createdBy", cascade = CascadeType.PERSIST)
private Set<Project> createdProject;
@JsonProperty(access = Access.WRITE_ONLY)
@OneToMany(mappedBy="modifiedBy", cascade = CascadeType.PERSIST)
private Set<Project> modifiedProject;
@JsonProperty(access = Access.WRITE_ONLY)
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "UserRole", joinColumns = @JoinColumn(name = "UserId"), inverseJoinColumns = @JoinColumn(name = "RoleId"))
private Set<Role> roles;
public User() {}
public User(Long id, String username, String password, String fullname,
String email, Boolean active, Boolean superuser, Set<Role> roles) {
super();
this.id = id;
this.username = username;
this.password = password;
this.fullname = fullname;
this.email = email;
this.active = active;
this.superuser = superuser;
this.roles = roles;
}
/* getters and setters */
}
ProjectController.java
package com.app.mycompany.AgileCenterServices.controller;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
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.RestController;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.app.mycompany.AgileCenterServices.entities.Project;
import com.app.mycompany.AgileCenterServices.entities.User;
import com.app.mycompany.AgileCenterServices.services.ProjectService;
import com.app.mycompany.AgileCenterServices.services.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/project")
public class ProjectController {
public static final Logger logger = LoggerFactory.getLogger(ProjectController.class);
@Autowired
ProjectService projectService;
@Autowired
UserService userService;
@CrossOrigin
@RequestMapping(value="/{id}", method = RequestMethod.PATCH)
public Project editProject(@RequestBody Map<String, String> project, @PathVariable("id") String id) throws Exception {
logger.info("Inside editProject() API ");
if(project == null) {
return null;
}
for(String key: project.keySet()) {
logger.info("Keys passed for update ==> Key(" + key + "): Value(" + project.get(key) + ")");
}
Project projectRec = null;
try {
logger.info("updateProject() :: Before save :: ");
projectRec = projectService.updateProjectInfo(project, id);
logger.info("updateProject() :: After save :: Saved successfully ::: ", projectRec.toString());
}
catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
logger.info("Leaving editProject() API");
return projectRec;
}
}
ProjectService.java (интерфейс)
package com.app.mycompany.AgileCenterServices.services;
import java.util.Map;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.app.mycompany.AgileCenterServices.entities.Project;
public interface ProjectService {
Project updateProjectInfo(Map<String, String> projectRec, String id) throws Exception;
Project createProject(Project project);
Project getProjectsByProjectName(String name);
Page<Project> listProjects(Pageable pageable);
Page<Project> searchProjects(String searchParam, Pageable pageable);
Project getProjectsByProjectId(String id);
}
ProjectServiceImpl.java
package com.app.mycompany.AgileCenterServices.services;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import com.app.mycompany.AgileCenterServices.entities.Project;
import com.app.mycompany.AgileCenterServices.entities.User;
import com.app.mycompany.AgileCenterServices.repository.ProjectRepository;
@Service
public class ProjectServiceImpl implements ProjectService {
@Autowired
ProjectRepository projectRepository;
private static final String nonUpdateable = "NON_UPDATEABLE";
private static final Map<String, Set<String>> nonUpdateableKeys = new HashMap<String, Set<String>>();
static {
Set<String> attributeNames = new HashSet<String>();
attributeNames.add("id");
attributeNames.add("CreatedBy");
attributeNames.add("CreatedDate");
nonUpdateableKeys.put(ProjectServiceImpl.nonUpdateable, attributeNames);
}
public static final Logger logger = LoggerFactory.getLogger(ProjectServiceImpl.class);
public Project updateProjectInfo(Map<String, String> projectRec, String id) throws Exception {
logger.info("Inside updateProjectInfo() API in ProjectServiceImpl");
Project dbRec = projectRepository.findOne(id);
if(dbRec == null) {
throw new Exception("No record found for the given project Id :: " + id);
}
logger.info("Record retrieved from db :: " + dbRec.toString());
setAttributesfromMaptoDBRec(projectRec, dbRec);
logger.info("DB record after updates :: " + dbRec.toString());
Project savedRecord = null;
try {
logger.info("Before save :: Input Project data ::: ", dbRec.toString());
savedRecord = projectRepository.save(dbRec);
logger.info("After save :: Saved successfully ::: ", savedRecord.toString());
}
catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
logger.info(" Leaving updateProjectInfo() API in ProjectServiceImpl");
return savedRecord;
}
private void setAttributesfromMaptoDBRec(Map<String, String> updatedProjectRecMap, Project updatedProjectRec) {
logger.info(" Inside setAttributesfromMaptoDBRec() API in ProjectServiceImpl");
Set<String> nonUpdateableAttributes = ProjectServiceImpl.nonUpdateableKeys.get(ProjectServiceImpl.nonUpdateable);
if(updatedProjectRecMap == null) {
logger.warn(" Input Map to service is null in ProjectServiceImpl ===> " + (updatedProjectRecMap == null));
return;
}
for(String key: updatedProjectRecMap.keySet()) {
logger.info(" Project property ===> " + key);
logger.info(" nonUpdateableAttributes contains key ===> " + nonUpdateableAttributes.contains(key));
if(!nonUpdateableAttributes.contains(key)) {
try {
if(key != null && "name".equalsIgnoreCase(key)) {
updatedProjectRec.setName(updatedProjectRecMap.get(key));
}
if(key != null && "description".equalsIgnoreCase(key)) {
updatedProjectRec.setDescription(updatedProjectRecMap.get(key));
}
if(key != null && "template".equalsIgnoreCase(key)) {
updatedProjectRec.setTemplate(updatedProjectRecMap.get(key));
}
if(key != null && "projectKey".equalsIgnoreCase(key)) {
updatedProjectRec.setProjectKey(updatedProjectRecMap.get(key));
}
if(key != null && "type".equalsIgnoreCase(key)) {
updatedProjectRec.setType(updatedProjectRecMap.get(key));
}
if(key != null && "category".equalsIgnoreCase(key)) {
updatedProjectRec.setCategory(updatedProjectRecMap.get(key));
}
} catch(Exception ex) {
logger.error("No key was found or key was null");
ex.printStackTrace();
}
}
}
}
AuditAwareImpl.java
package com.app.mycompany.AgileCenterServices.audit;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import javax.persistence.EntityManager;
import com.app.mycompany.AgileCenterServices.repository.UserRepository;
public class AuditorAwareImpl implements AuditorAware<com.app.mycompany.AgileCenterServices.entities.User> {
public static final Logger logger = LoggerFactory.getLogger(AuditorAwareImpl.class);
@Autowired
EntityManager entityManager;
@Override
public com.app.mycompany.AgileCenterServices.entities.User getCurrentAuditor() {
logger.info("Inside getCurrentAuditor() API");
String user = ((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
logger.info("Inside getCurrentAuditor() :: Username :: " + user);
String userInfoQuery =
"select u.id, u.username, u.password, u.superuser, u.fullname, u.email, u.active from User u " +
"where upper(u.username) = upper('" + user + "')";
com.app.mycompany.AgileCenterServices.entities.User userResponse = null;
try {
// userResponse = userRepository.findOneByUsername(user);
userResponse = (com.app.mycompany.AgileCenterServices.entities.User) entityManager.createNativeQuery(userInfoQuery.toString(), com.app.mycompany.AgileCenterServices.entities.User.class)
.getSingleResult();
logger.info("Response after fetching complete userInformation" + userResponse.toString());
} catch(NoResultException e) {
System.out.println("No records found");
e.printStackTrace();
throw new NoResultException();
} catch (Exception ex) {
ex.printStackTrace();
}
return userResponse;
}
}
Исключение трассировки стека
2019-02-26 13:56:38.878 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.controller.ProjectController : Inside editProject() API
2019-02-26 13:56:38.879 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.controller.ProjectController : Keys passed for update ==> Key(type): Value(Business)
2019-02-26 13:56:38.879 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.controller.ProjectController : updateProject() :: Before save ::
2019-02-26 13:56:38.879 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.services.ProjectServiceImpl : Inside updateProjectInfo() API in ProjectServiceImpl
2019-02-26 13:56:38.905 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.services.ProjectServiceImpl : Record retrieved from db :: Project [id=1000, name=Content Server Platform, description= Enterprise document repository, template=Scrum, projectKey=OCCN, type=Software, category=Platform, projectLead=com.app.mycompany.AgileCenterServices.entities.User@76274998, active=true, createdBy=com.app.mycompany.AgileCenterServices.entities.User@76274998, createdDate=2019-02-26 13:54:34.0, modifiedBy=com.app.mycompany.AgileCenterServices.entities.User@76274998, modifiedDate=2019-02-26 13:54:34.0]
2019-02-26 13:56:38.905 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.services.ProjectServiceImpl : Inside setAttributesfromMaptoDBRec() API in ProjectServiceImpl
2019-02-26 13:56:38.905 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.services.ProjectServiceImpl : Project property ===> type
2019-02-26 13:56:38.906 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.services.ProjectServiceImpl : nonUpdateableAttributes contains key ===> false
2019-02-26 13:56:38.906 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.services.ProjectServiceImpl : DB record after updates :: Project [id=1000, name=Content Server Platform, description= Enterprise document repository, template=Scrum, projectKey=OCCN, type=Business, category=Platform, projectLead=com.app.mycompany.AgileCenterServices.entities.User@76274998, active=true, createdBy=com.app.mycompany.AgileCenterServices.entities.User@76274998, createdDate=2019-02-26 13:54:34.0, modifiedBy=com.app.mycompany.AgileCenterServices.entities.User@76274998, modifiedDate=2019-02-26 13:54:34.0]
2019-02-26 13:56:38.906 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.services.ProjectServiceImpl : Before save :: Input Project data :::
2019-02-26 13:56:38.912 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.audit.AuditorAwareImpl : Inside getCurrentAuditor() API
2019-02-26 13:56:38.922 INFO 12432 --- [http-nio-8080-exec-6] c.a.m.A.audit.AuditorAwareImpl : Inside getCurrentAuditor() :: Username :: dyoung
c.a.m.A.audit.AuditorAwareImpl : Inside getCurrentAuditor() :: Username :: dyoung
c.a.m.A.audit.AuditorAwareImpl : Response after fetching complete userInformationcom.app.mycompany.AgileCenterServices.entities.User@76274998
В дополнительных примечаниях по отладке показано следующее исключение
javax.persistence.PersistenceException: org.hibernate.HibernateException: найдены общие ссылки на коллекцию: com.app.mycompany.AgileCenterServices.entities.User.createdProject в org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert (AbstractEntityManagerImpl.java:1692) в org.hibernate.jpa.spi.AbstractEntityManagerInt.nt.nt.nt.nt.nt.nt.nt.nt1.tmphibernate.jpa.spi.AbstractEntityManagerImpl.convert (AbstractEntityManagerImpl.java:1608) в org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush (AbstractEntityManagerImpl.java:1mpery.j.Imp.Ibe.QuI.Java: 518) в org.hibernate.jpa.internal.QueryImpl.getSingleResult (QueryImpl.java:527) в com.app.mycompany.AgileCenterServices.audit.AuditorAwareImpl.getCurrentAuditor (AuditorAwareImpl.java:49) в com.app.mycompany.AgileCenterServices.audit.AuditorAwareImpl.getCurrentAuditor (AuditorAwareImpl.java:18) 1066 *