I have a many-to-many relation in my project and although I'm able to write in my two Entities table, the relational table does not get anything written.
Here's how I'm declaring this using JPA annotations:
Professor.java
#Entity
#Table(name = "Professor")
public class Professor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "idProfessor", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"),
inverseJoinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"))
private List<Aluno> alunoList;
// getters and setters
}
Aluno.java
#Entity
#Table(name = "Aluno")
public class Aluno implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "idAluno", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(mappedBy = "alunoList", fetch = FetchType.EAGER)
private List<Professor> professorList;
// getters and setters
}
And here is the service layer to insert into database:
#Autowired
private AlunoDao alunoDao;
#Autowired
private ProfessorDao professorDao;
#RequestMapping(value = RestUriConstants.SUBMETER, method = RequestMethod.POST)
public #ResponseBody JsonResponse submeter(#RequestBody final Aluno aluno) {
Professor professor = professorDao.find(1);
aluno.setProfessorList(Arrays.asList(professor));
alunoDao.persist(aluno);
...
}
In this case, please consider that I already have an entry with id "1" for Professor.
As I said, it does write on Aluno and Professor table but does NOT write anything into ALUNO_PROFESSOR table.
I've already taken a look at these three kind of similiar questions but none of them could help me:
Hibernate and Spring: value of many-to-many not inserted into generated table
JPA many-to-many persist to join table
How to persist #ManyToMany relation - duplicate entry or detached entity
EDIT - Adding more code snippets
JpaAlunoDao.java
#Repository
public class JpaAlunoDao implements AlunoDao {
#PersistenceContext
private EntityManager em;
#Transactional
public void persist(Aluno aluno) {
em.persist(aluno);
}
}
JpaExercicioDao.java
#Repository
public class JpaExercicioDao implements ExercicioDao {
#PersistenceContext
private EntityManager em;
#Transactional
public void persist(Exercicio exercicio) {
em.persist(exercicio);
}
}
Try this:
public class Professor {
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"),
inverseJoinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"))
private List<Aluno> alunoList;
}
public class Aluno {
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"),
inverseJoinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"))
private List<Professor> professorList;
}
This will ensure that the metadata for the many-to-many relationship is available on both the entities and that operations on either side of the relationship are cascaded to the other side.
I also suggest replacing FetchType.EAGER with FetchType.LAZY for better performance because this has the potential of loading a very large dataset.
I have the same issue. I swapped where the full mapping declare to the class that we will use save() function on.
In your case:
public class Aluno {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idAluno"),
inverseJoinColumns = #JoinColumn(name = "idProfessor")
private List<Professor> professorList;
}
public class Professor {
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "professorList",)
private List<Aluno> alunoList;
}
and It worked fine.
...
Normally, Hibernate holds the persistable state in memory. The process of synchronizing this state to the underlying DB is called flushing.
When we use the save() method, the data associated with the save operation will not be flushed to the DB unless and until an explicit call to flush() or commit() method is made.
If we use JPA implementations like Hibernate, then that specific implementation will be managing the flush and commit operations.
One thing we have to keep in mind here is that, if we decide to flush the data by ourselves without committing it, then the changes won't be visible to the outside transaction unless a commit call is made in this transaction or the isolation level of the outside transaction is READ_UNCOMMITTED.
...
From Difference Between save() and saveAndFlush() in Spring Data JPA by baeldung:
https://www.baeldung.com/spring-data-jpa-save-saveandflush
employeeRepository.saveAndFlush(new Employee(2L, "Alice"));
or
employeeRepository.save(new Employee(2L, "Alice"));
employeeRepository.flush();
Its not necessary to set many-to-many relationship on both entities.
Just remove session.setFlushMode(FlushMode.MANUAL);
By default HibernateTemplate inside of Spring set FlushMode.MANUAL
this is source code from HibernateTemplate.
if (session == null) {
session = this.sessionFactory.openSession();
session.setFlushMode(FlushMode.MANUAL);
isNew = true;
}
Some times the problem is in the way that you insert the values. I explain with an example.
User user = userFacade.find(1);
Post post = new Post("PRUEBA");
user.addPostCollection(post);
post.addUserCollection(user);
postFacade.create(post);
You have to add the post in postCollection and the user in userCollection. You have two add the correspond entity in the collections of the two entities.
Class USER
public void addPostCollection(Post post) {
if(postCollection == null){
postCollection = new ArrayList<Post>();
}
postCollection.add(post);
}
#ManyToMany(mappedBy = "userCollection")
private Collection<Post> postCollection;
Class Post
public void addUserCollection(User user){
if(userCollection == null){
userCollection = new ArrayList<User>();
}
userCollection.add(user);
}
#JoinTable(name = "USER_POST_R", joinColumns = {
#JoinColumn(name = "POSTID", referencedColumnName = "ID")}, inverseJoinColumns = {
#JoinColumn(name = "USERID", referencedColumnName = "ID")})
#ManyToMany
private Collection<User> userCollection;
Also, it is important to instance the list, for example userCollection = new ArrayList(). If you do not, the value won´t insert.
Related
I'm trying implement soft delete in a #OneToMany relationship in an academic project. I need to mark as "deleted" a child entity in the following scenarios:
When the parent entity is deleted (soft deleted as well).
When the child entity is no longer referenced by his parent.
I've achieved this requirement using the #SqlDelete annotation, and the CascadeType.all / orphanRemoval = true options on the #OneToMany side as follow:
Parent
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql = "update Discount set deleted = true where id = ?")
#Where(clause = "deleted = false")
public class Discount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
/// More properties ...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "discountId", nullable = false)
private List<PetrolStationDiscount> petrolStationDiscounts = new ArrayList<>();
public Discount() {}
// Getters and setters...
public List<PetrolStationDiscount> getPetrolStationDiscounts() {
return petrolStationDiscounts;
}
public void setPetrolStationDiscounts(List<PetrolStationDiscount> petrolStationDiscounts) {
this.petrolStationDiscounts = petrolStationDiscounts;
}
// HashCode & Equals methods
Child Entity
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql="UPDATE PetrolStationDiscount SET deleted = true WHERE id = ?")
public class PetrolStationDiscount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
#ManyToOne
#JoinColumn(name = "petrolStationId")
private PetrolStation petrolStation;
#Column(insertable = false, updatable = false)
private String discountId;
// Getters & Setters -- HashCode & Equals methods
BaseDAO
#MappedSuperclass
public abstract class BaseDao {
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date createdAt;
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date updatedAt;
#Column(name = "deleted", nullable = false)
private boolean deleted = false;
// Getters and Setters
}
This works fine, but I'm not comfortable mixing the sql statement straight into the entities, so I've tried the approach I found in the following thread:
How to soft delete parent and child together (cascade) using spring boot with jpa
Following the thread, I've created a BaseRepository overriding the CrudRepository methods "delete" and "deleteById", but it doesn't work:
When the parent entity is deleted, only the parent deleted field is saved as true.
When a child entity is no longer referenced by his parent, if orphanRemoval is set to true, the child entity is hard deleted, and if false, nothing happened.
#NoRepositoryBean
public interface BaseRepository<T extends BaseDao> extends CrudRepository<T, Serializable> {
#Override
#Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
#Transactional
#Modifying
void deleteById(Serializable id);
#Override
#Transactional
default void delete(T entity) {
deleteById(entity.getId());
}
¿Someone could help me?
A lot of thanks!
I'm new to JPA and I'm stumped on how to capture the parent id param for nested children. I have two entities, Data and Property. Right now I successfully managed to create a one to many mapping from Data to Property. The thing is that Property can have its own children of itself. The "first layer" properties are able to get and save the data id value, but properties nested in another property are unable to do so. I was wondering if there is a method to do something like this since I could not find any information on it online.
Data (parent class)
#Entity
#Table(name = "data")
public class Data implements Serializable {
// omitted non relevant code
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#OneToMany(targetEntity = EventProperty.class, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "data_id", referencedColumnName = "id")
private List<EventProperty> properties;
}
Property (child class)
#Entity
#Table(name = "property")
public class Property implements Serializable {
private static final long serialVersionUID = -1100374255977700675L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#ManyToOne
#JoinColumn(name = "data_id", referencedColumnName = "id")
private EventData eventData;
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private EventProperty parent;
#OneToMany(targetEntity = EventProperty.class, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private List<EventProperty> children;
}
I'm wondering if storing the properties as a List in Data is the right way to go, or if there's any way to set data_id of a child Property to be the same as the parent Property if it is null, or if there's some other correct way that I am not aware of. Any help would be greatly appreciated!
I am getting the exception while updating the Parent entity with a new Child entity,
Here is my Sample Table
public class NavigationNode implements Serializable,Auditable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "NodeID")
private Long id;
#Embedded
private AuditSection auditSection = new AuditSection();
#Column(name = "Code", nullable = false, unique = true)
private String navCode;
#Column(name = "NodeTitle")
private String title;
#OneToMany(fetch=FetchType.LAZY, cascade = {CascadeType.PERSIST})
#JoinTable(name = "node2linkRel", joinColumns = { #JoinColumn(name = "NavigationNodeID") }, inverseJoinColumns = { #JoinColumn(name = "LinkID") })
private Collection<Link> links;
#ElementCollection
#OneToMany(fetch=FetchType.LAZY, orphanRemoval = true, cascade = {CascadeType.PERSIST} )
#JoinTable(name = "node2nodeRel", joinColumns = { #JoinColumn(name = "ParentID") }, inverseJoinColumns = { #JoinColumn(name = "ChildID") })
private Collection<NavigationNode> children;
#Column(name = "Visible")
private Boolean visible;
}
There is another Table called Link
public class Link implements Serializable,Auditable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "LinkID")
private Long id;
#Embedded
private AuditSection auditSection = new AuditSection();
#Column(name = "LinkUrl")
private String url;
#Column(name = "linkName")
private String linkName;
#Column(name = "VisibleInMenu")
private Boolean visibleInMenu;
#ManyToOne(cascade = {CascadeType.PERSIST})
#JoinTable(name = "node2linkRel", joinColumns = { #JoinColumn(name = "LinkID") }, inverseJoinColumns = { #JoinColumn(name = "NavigationNodeID") })
private NavigationNode node;
}
While Saving without the Links I am getting the above error.
failed to lazily initialize a collection of role: Navigation.links
could not initialize proxy
My requirements are
I can created a Navigaiton with optional children and links, Update an Child/parent with optional children and links.
If I delete Navigation Node then the related children should be deleted but I want to keep only the Link and delete the relation between Navigation and Link.
If I try to delete the LInk record, only the relation between Navigation and link should be deleted.
How Can I achieve that and is this the correct Table configuration.
Also While deleting the Link or Node I am getting another exception.
Please suggest.
Thanks
This is because I was trying to Assign a new Object List to the Links in navigation Table after getting the Object from the DataBase.
I was trying to break a Relation between a Link and Navigation.
For achieving this I just remove the Link from the Links list in the Navigaition Table and save, so that the Relation between those entity breaks.
My requirement is not to delete the Link if I try to unlink the relation between Navigation and Link.
I have a problem with my code in #Manytomany hibernate relationship.
I see above issue is due to "CascadeType.ALL" on #ManyToMany relationship side. But if i remove this CascadeType.ALL in #ManyToMany. If I change this CascadeType.ALL in #ManyToMany with CascadeType.PERSIST and CascadeType.MERGE, I solve the problem. But when I add new an object will get an error. otherwise, if I change it to CascadeType.ALL, when adding new objects it works fine but it doesn't solve the problem I'm discussing. So how can I keep the rows in the Class Tag when deleting rows in Post.
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
public Post() {}
public Post(String title) {
this.title = title;
}
#ManyToMany(cascade = {
CascadeType.ALL
},fetch = FetchType.EAGER)
#JoinTable(name = "post_tag",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<>();
}
//
public class Tag {
#Id
#GeneratedValue
private Long id;
#NaturalId
private String nameTag;
#ManyToMany(mappedBy = "tags",fetch = FetchType.LAZY)
private List<Post> posts = new ArrayList<>();
public Tag() {}
public Tag(String nameTag) {
this.nameTag = nameTag;
}
}
As I said above if it is CascadeType.ALL I will not be able to retain the row in Tag when I delete a row in Post
when I write it is CascadeType.PERSIST and CascadeType.MERGE i get an error:
object references an unsaved transient instance - save the transient instance before flushing.
And this is my input API object:
{
"title": "System",
"tags": [
{
"nameTag": "apt"
},
{
"nameTag":"Worm"
}
]
}
Remember that the api input works fine when I use Casecade.ALL
I am working on a project management system which has three users namely employee, Manager and HRM. The employee and Manager are in the same entity having many-to-many recursive relationship(let's call this Employee entity). The Employee entity and the HRM entity inherits a User entity. The hibernate inheritance strategy used here is single-table.
Initially when a user is registered he is saved as an instance of User( Repository of type User). I want to update the user instance to an instance of an employee or instance of Manager when he is assigned to a particular project. How can this be implemented using spring data jpa. I am doing the project using spring boot.
I have created the entities using java classes and mapped each entity.
I have not provided the Project and Tasks class in the following code. If necessary I can provide.
Following is the User class.
#Entity
#Table(name = "users")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name="User_Type",
discriminatorType=DiscriminatorType.STRING
)
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="user_id")
private Long id;
private String name;
private String email;
private String password;
private String mobilenumber;
private String gender;
private String resetToken;
#ManyToMany(fetch = FetchType.LAZY, cascade={CascadeType.ALL})
#JoinTable(name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();//roles refer to the employee,manager and HRM roles
//public getters and setters
Following is the inherited Employee class
#Entity
#DiscriminatorValue("Employee")
public class Employee extends User {
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinTable(name = "employee_project",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "project_id")
)
private Set<Project> project = new HashSet<>();
#ManyToMany
#JoinTable(name = "employee_tasks",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "task_id")
)
private Set<Task> tasks = new HashSet<>();
#ManyToMany
#JoinTable(name = "rm_employee",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "rm_id")
)
private Set<Employee> managers = new HashSet<>();
#ManyToMany
#JoinTable(name = "rm_employee",
joinColumns = #JoinColumn(name = "rm_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private Set<Employee> employees = new HashSet<>();
//public getters and setters
I tried the following which is to downcast the instance of User to the instance of Employee which results in CastException.
Optional<User> optional = userRepo.findById(id);
Employee employee = (Employee)optional.get();
following is a sketch of the er model
I was able to solve the question without using inheritance. Actually I had to avoid inheritance since as java is concerned the type cannot be changed from one to another. As in this example the type User cannot be converted to Employee or Manager as you wish. So the solution is to create two separate classes for the Manager and Employee(This should not extend the User class as I have done in the question) . These two should be annotated as Entity and should have an Id field. You can have a constructor with the above said Id field in both classes and when ever you assign a user to a project you can use this constructor depending on whether he is manager or employee to create a respective instance of manager or employee.
the employee class is as follows
public class Employee{
#Id
private Long employeeId;
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinTable(name = "employee_project",
joinColumns = #JoinColumn(name = "employee_id"),
inverseJoinColumns = #JoinColumn(name = "project_id")
)
private Set<Project> project = new HashSet<>();
#ManyToMany
#JoinTable(name = "employee_rm",
joinColumns = #JoinColumn(name = "employee_id"),
inverseJoinColumns = #JoinColumn(name = "rm_id")
)
private Set<Manager> managers = new HashSet<>();
#ManyToMany
#JoinTable(name = "employee_tasks",
joinColumns = #JoinColumn(name = "employee_id"),
inverseJoinColumns = #JoinColumn(name = "task_id")
)
private Set<Task> tasks = new HashSet<>();
public Employee() {}
public Employee(Set<Project> project, Set<Manager> managers, Set<Task> tasks, Long employeeId ) {
super();
this.project = project;
this.managers = managers;
this.tasks = tasks;
this.employeeId=employeeId;
}
//with public getters and setters
and the Managers class is as follows
#Entity
public class Manager {
#Id
private Long managerId ;
#ManyToMany
#JoinTable(name = "manager_employee",
joinColumns = #JoinColumn(name = "manager_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private Set<Employee> employees = new HashSet<>();
#OneToOne
#JoinColumn(name="project_id")
private Project project;
public Manager() {}
public Manager(Long managerId) {
super();
this.managerId = managerId;
}
//public getters and setters