Reputation: 748
I'm using Java Spring Boot to create an API. I'm trying to create a bridge table between two entities: Clients and Users. One client can have multiple users. My User entity looks like this:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.hibernate.annotations.NaturalId;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users", uniqueConstraints = {
@UniqueConstraint(columnNames = {
"username"
}),
@UniqueConstraint(columnNames = {
"email"
})
})
public class User{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@NotBlank
@Size(max = 40)
@Column(name = "name")
private String name;
@NotBlank
@Size(max = 15)
@Column(name = "username")
private String username;
@NaturalId
@NotBlank
@Size(max = 40)
@Email
@Column(name = "email")
private String email;
@NotBlank
@Size(max = 100)
@Column(name = "password")
private String password;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
// @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public User() {
}
public User(String name, String username, String email, String password) {
this.name = name;
this.username = username;
this.email = email;
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
And in the Client entity where I create my bridge table, I'm doing the next:
@Entity
@Table(name = "cliente")
public class Cliente {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "empresa")
private String empresa;
@Column(name = "telefono")
private Integer telefono;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(unique = true)
private Licencia licencia;
@OneToMany(cascade = CascadeType.ALL ,fetch = FetchType.EAGER)
@JoinTable(name = "user_cliente",
joinColumns = @JoinColumn(name = "cliente_id"),
inverseJoinColumns = @JoinColumn(name = "user_id"))
private List<User> users = new ArrayList<>();
public Cliente(String empresa, Integer telefono) {
this.empresa = empresa;
this.telefono = telefono;
}
public Cliente() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmpresa() {
return empresa;
}
public void setEmpresa(String empresa) {
this.empresa = empresa;
}
public Integer getTelefono() {
return telefono;
}
public void setTelefono(Integer telefono) {
this.telefono = telefono;
}
public Licencia getLicencia() {
return licencia;
}
public void setLicencia(Licencia licencia) {
this.licencia = licencia;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
This creates my bridge_table in MySQL using id's as foreign keys. Now, when I want to assing a User to a Client, I'm doing the next
User user = new User(userModel.getName(), userModel.getUsername(), userModel.getEmail(), userModel.getPassword());
Cliente cliente = clienteRepository.findById(6);
List<User> users = cliente.getUsers();
users.add(user);
cliente.setUsers(users);
clienteRepository.save(cliente);
At the moment of save the Client object using the clienteRepository.save() method, it throws me the next exception:
java.lang.UnsupportedOperationException: null
at java.base/java.util.Collections$1.remove(Collections.java:4712) ~[na:na]
at java.base/java.util.AbstractCollection.clear(AbstractCollection.java:447) ~[na:na]
at org.hibernate.collection.internal.PersistentSet.clear(PersistentSet.java:318) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:545) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.type.CollectionType.replace(CollectionType.java:721) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:166) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:393) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:203) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:176) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:923) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:893) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:261) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:490) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:415) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:523) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:455) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:418) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:149) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:460) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:202) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:176) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:69) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:901) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350) ~[spring-orm-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at com.sun.proxy.$Proxy116.merge(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308) ~[spring-orm-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at com.sun.proxy.$Proxy116.merge(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:510) ~[spring-data-jpa-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295) ~[spring-tx-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ~[spring-tx-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:138) ~[spring-data-jpa-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) ~[spring-data-commons-2.1.9.RELEASE.jar:2.1.9.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at com.sun.proxy.$Proxy153.save(Unknown Source) ~[na:na]
at com.pruebas.spring_jdbc.Controllers.UserController.newUser(UserController.java:92) ~[classes/:na]
at com.pruebas.spring_jdbc.Controllers.UserController$$FastClassBySpringCGLIB$$55750dd8.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:119) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.8.RELEASE.jar:5.1.8.RELEASE]
Why is this happening? Is something wrong with the way I create the bridge table on the Entity or is something related to the list or the way of saving it in MySQL?
Upvotes: 1
Views: 5739
Reputation: 26026
It's because the list isn't fully initialized as a result of using fetch = FetchType.LAZY
. You're trying to append element to proxy over not fetched list and save that.
You can fix this ugly by setting eager fetching: fetch = FetchType.EAGER
or use a transaction. Cleaner solution would be to fetch it using for example jpql:
select c from Client c left join fetch c.users where id = :id
Upvotes: 3