Springboot JPA -分离实体传递到持久化

azpvetkf  于 2023-10-19  发布在  Spring
关注(0)|答案(1)|浏览(112)

我正在用我以前的JPA知识学习Springboot。目前正在尝试使用MySQL DB创建一个简单的Web应用程序。首先,我创建了两个表usersusers_session_data
users_session_data具有OneToOne关系,这意味着每个用户可以同时拥有会话数据。现在我想创建一个用户,但它给了我以下例外:

org.hibernate.PersistentObjectException: detached entity passed to persist: com.simpleapp.entities.EntityUsersSessionData
    at org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:88)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:77)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:54)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:755)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:739)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:360)
    at jdk.proxy2/jdk.proxy2.$Proxy137.persist(Unknown Source)
    at com.simpleapp.core.AbstractFacade.createRecord(AbstractFacade.kt:100)
    at com.simpleapp.flows.FlowUsersSessions.create(FlowUsersSessions.kt:42)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
    at com.simpleapp.flows.FlowUsersSessions$$SpringCGLIB$$0.create(<generated>)
    at com.simpleapp.flows.FlowUsers.login(FlowUsers.kt:71)
    at com.simpleapp.controllers.restful.services.users.ServiceUsersLogin.getData(ServiceUsersLogin.kt:38)
    at com.simpleapp.controllers.restful.services.users.ServiceUsersLogin.getData(ServiceUsersLogin.kt:12)
    at com.simpleapp.core.restful.AbstractRestService.getResponse(AbstractRestService.kt:26)
    at com.simpleapp.controllers.restful.ControllerUsers.getUserLogin(ControllerUsers.kt:34)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at com.simpleapp.filters.AppSecurityFilter.doFilter(AppSecurityFilter.kt:62)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at com.simpleapp.filters.AppSecurityFilter.doFilter(AppSecurityFilter.kt:62)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:833)

我的课程如下:
BaseEntity

@MappedSuperclass
abstract class BaseEntity : java.io.Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID", nullable = false)
  open var id: Int? = null

  @Basic(optional = false)
  @Column(name = "STATUS", nullable = false)
  @Enumerated(EnumType.STRING)
  open var status: DBEntryStatusTypeEnum = DBEntryStatusTypeEnum.ACTIVE

  @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Utils.JSON_DATE_FORMAT)
  @Basic(optional = false)
  @Column(name = "CREATED_AT", nullable = false)
  @Temporal(TemporalType.DATE)
  open var createdAt = Date()

  override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (javaClass != other?.javaClass) return false

    other as BaseEntity
    if (id != other.id) return false
    if (status != other.status) return false
    return true
  }

  override fun hashCode(): Int {
    var result = id ?: 0
    result = 31 * result + (status.hashCode())
    return result
  }
}

BaseUserEntity

@MappedSuperclass
open class BaseUserEntity : BaseEntity() {

  @Id
  @Basic(optional = false)
  @Column(name = "ID", nullable = false)
  @JsonIgnore
  @XmlTransient
  override var id: Int? = null

  @JsonIgnore
  @XmlTransient
  @JoinColumn(name = "ID", referencedColumnName = "ID", nullable = false, insertable = false, updatable = false)
  @OneToOne(optional = false, fetch = FetchType.LAZY)
  open var users: EntityUsers? = null

}

EntityUsers

@Entity
@Table(name = "users")
@XmlRootElement
class EntityUsers : BaseEntity() {

  @Column(name = "NICK", length = 255)
  var nick: String? = null

  @Basic(optional = false)
  @Column(name = "NAME_SURNAME", nullable = false, length = 255)
  var nameSurname: String? = null

  @Column(name = "ABOUT", length = 300)
  var about: String? = null

  @Column(name = "BIRTHDAY")
  @Temporal(TemporalType.DATE)
  var birthday: Date? = null

  @Column(name = "LAST_LOGIN")
  @Temporal(TemporalType.TIMESTAMP)
  var lastLogin = Date()

  @XmlTransient
  @JsonIgnore
  @OneToOne(cascade = [CascadeType.ALL], mappedBy = "users", fetch = FetchType.LAZY)
  var usersSessionData: EntityUsersSessionData? = null

}

EntityUsersSessionData

@Entity
@Table(name = "users_session_data")
@XmlRootElement
class EntityUsersSessionData : BaseUserEntity() {

  @Basic(optional = false)
  @Column(name = "TOKEN", nullable = false, length = 255)
  var token: String? = null

}

FlowUsers

@Component
class FlowUsers {

  @Autowired
  @org.springframework.context.annotation.Lazy
  lateinit var daoFactory: DaoFactory

  @Autowired
  @org.springframework.context.annotation.Lazy
  lateinit var flowUserSessions: FlowUsersSessions

  /**
   * Returns dao object of users.
   */
  fun getDao() = daoFactory.getDaoUsers()

  /**
   * Checks if user exists and if exists returns user, if not, creates user then returns.
   */
  fun login(data: RequestModelUserLogin, ipAddress: String?): EntityUsers? {
    // Get user from the token via Firebase.
    val userData = FirebaseClientAuth.getCredentialsForToken(data.token) ?: return null

    // Otherwise create the user.
    var user = EntityUsers().apply {
      avatar = userData.photoUrl
      nameSurname = userData.displayName
      birthday = null // This will be filled by user.
      email = if (userData.email.isNullOrEmpty()) data.email else userData.email
    }

    // attached new id to the mUser (by creating database row instance)
    user = getDao().createRecord(user)

    // creates session for the first time for user and attaches it to user
    user.usersSessionData = flowUserSessions.create(user)
    return getDao().updateRecord(user)
  }
}

FlowUsersSessions

@Component
class FlowUsersSessions {

  @Autowired
  @org.springframework.context.annotation.Lazy
  lateinit var daoFactory: DaoFactory

  @Autowired
  @org.springframework.context.annotation.Lazy
  lateinit var flowUsers: FlowUsers

  /**
   * Returns the dao object of user sessions.
   */
  fun getDao() = daoFactory.getDaoUsersSessions()

  /**
   * Returns a session for the given token.
   */
  fun getByToken(token: String) = getDao().getByToken(token = token)

  /**
   * Creates a user session entity, right after creating the user.
   */
  fun create(user: EntityUsers): EntityUsersSessionData? {
    val record = EntityUsersSessionData().apply {
      id = user.id
      status = user.status
      createdAt = Date()
      token = UUID.randomUUID().toString()
    }
    return getDao().createRecord(record)
  }
}

我尝试将CascadeType.ALL替换为CascadeType.PERSIST和CascadeType.MERGE,但没有成功。

5kgi1eie

5kgi1eie1#

发生这种情况的原因是,您正在持久化一个新的PsychologyUsers,但已将现有(分离的)PsychologyUsersSessionData包括在用户的usersSessionData中,并且您将Map设置为级联持久化操作。这与在现有的EntityUsersSessionData示例上调用entityManager.persist(entityUsersSessionData)相同-因为它已经存在,所以需要JPA抛出错误。
您需要确定要对这些IdentyUsersSessionData示例执行什么操作;你想不想把它们合并进去最简单的解决方案是不要在Map上放置cascade选项:

@XmlTransient
  @JsonIgnore
  @OneToOne(mappedBy = "users", fetch = FetchType.LAZY)
  var usersSessionData: EntityUsersSessionData? = null

在Map上应用CascadeType选项时要谨慎,并且只在需要时才应用,因为它们对应用程序的影响并不总是预先被注意到。

相关问题