I want to create two class which has #OneToMany and #ManyToOne relationship. Below is a sample code.
#Entity
public class ClassA {
#Id
public Long id;
#ManyToOne
public ClassB classB;
public String something;
}
#Entity
public class ClassB {
#Id
public Long id;
#OneToMany(mappedby = "classB")
public List<ClassA> classAList = new ArrayList<>();
public String somet;
}
That code works just fine but, I want to have the last reference of class A in class B. I can do that by adding #OneToOne public ClassA lastClassA; in ClassB (just like here). Below is the code:
#Entity
public class ClassA {
#Id
public Long id;
#ManyToOne
public ClassB classB;
public String something;
}
#Entity
public class ClassB {
#Id
public Long id;
#OneToMany(mappedby = "classB")
public List<ClassA> classAList = new ArrayList<>();
#OneToOne
public ClassA lastClassA;
public String somet;
}
That code is working. The problem occurs when I insert data to either of it, I got the following error: foreign key constraint fails. That happened because I don't have a reference in another table.
It is not clear exactly what you want to do however if you want a reference to the final item in the collection there are some options that could work.
Mark the collections as 'extra-lazy' which will allow you to access the last item in the list without loading the whole collection. If you do require to load the other items in the collection this may be problematic however.
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html
"Extra-lazy" collection fetching: individual elements of the
collection are accessed from the database as needed. Hibernate tries
not to fetch the whole collection into memory unless absolutely
needed. It is suitable for large collections.
If you only require the last item then use a Hibernate #Where clause to filter the collection
https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/
Create a database view to filter the most recent items and map an additional entity to that.
Related
I have the following simple application
Users Entity
#Entity
public class Users implements Serializable {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRole Entity
#Entity
public class UserRole implements Serializable {
#Id
#GeneratedValue
private long id;
private String roleName;
#OneToMany(mappedBy = "userrole", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRoleUser Many to many resolver class
#Entity
public class UserRoleUser implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "fk_userId")
private Users user;
#ManyToOne
#JoinColumn(name = "fk_userroleId")
private UserRole userrole;
// GETTERS AND SETTERS
}
UserRoleUserRepository
#Repository
#Transactional
public interface UserRoleUserRepository extends JpaRepository<UserRoleUser, Long>, QueryDslPredicateExecutor<UserRoleUser>{
}
Main Application class
#SpringBootApplication
#Configuration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
UserRoleUserRepository userRoleUserRepository = context.getBean(UserRoleUserRepository.class);
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
}
}
On running the main application, the database records in the UserRoleUser table are not being deleted. What could be the issue? I am using Spring Data and QueryDsl.
I have also tried putting the delete functionality on a Controller but still doesn't work.
#RestController
#RequestMapping("/api")
public class DeleteController {
#Autowired
UserRoleUserRepository userRoleUserRepository;
#GetMapping("/delete")
public String delete() {
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
return new Date().toString();
}
}
If you need to use the given methods provided by CrudRepository, use the JpaRepository.deleteInBatch(). This solves the problem.
The problem is the entities are still attached and will not be deleted until they become detached. If you delete by their id instead of the entity itself, it will delete them.
One thing I noticed is you are deleting the users one at a time which could lead to a database performance hit as the query will be recreated each time. The easiest thing to do is to add all the ids to a set then delete the set of ids. Something like this:
Set<Integer> idList = new HashSet<>();
for (UserRoleUser userRoleUser : findAll) {
idList.add(userRoleUser.getId());
}
if (!idList.isEmpty()) {
userRoleUserRepository.delete(idList);
}
then in your repository add the delete method
#Modifying
#Query("DELETE FROM UserRoleUser uru WHERE uru.id in ?1")
#Transactional
void delete(Set<Integer> id);
The reason why the child objects (UserRoleUser) are not being deleted upon userRoleUserRepository.delete(userRoleUser) call is that each UserRoleUser points to a Users which in turn holds a #OneToMany reference Set<UserRoleUser> userRoleUser.
As described in this StackOverflow answer, what your JPA implementation (e.g. Hibernate) effectively does is:
The cache takes note of the requested child exclusion
The cache however does not verify any changes in Set<UserRoleUser>
As the parent #OneToMany field has not been updated, no changes are made
A solution would go through first removing the child element from Set<UserRoleUser> and then proceed to userRoleUserRepository.delete(userRoleUser) or userRepository.save(user)
In order to avoid this complication two answers have been provided:
Remove element by Id, by calling userRoleUserRepository.deleteById(userRoleUser.getId()) : in this case the entity structure (and therefore the parent) is not checked before deletion. In the analog case of deleteAll something more convoluted such as userRoleUserRepository.deleteByIdIn(userRoleUserList.stream().map(UserRoleUser::getId).collect(Collectors.toList())) would have to be employed
Convert your CrudRepository to a JpaRepository and use its deleteInBatch(userRoleUserList) method. As explained in this article and this StackOverflow answer the deleteInBatch method tries to delete all records at once, possibly generating a StackOverflow error in the case the number of records is too large. As repo.deleteAll() removes one record at a time this error it minimizes this risk (unless the call is itself inside a #Transactional method)
According to this StackOverflow answer, extra care should be used when recurring to deleteInBatch as it:
Does not cascade to other entities
Does not update the persistence context, requiring it to be cleared (the method bypasses the cache)
Finally , as far as I know , there is no way this could be done by simply calling userRoleUserRepository.delete(userRoleUser) without first updating the parent object. Any updates on this (whether by allowing such behaviour through annotations, configuration or any other means) would be a welcome addition to the answer.
I have got two entities with Many-to-one (One-to-many) relationship.
Class ParserEntity:
import javax.persistence.*;
#Entity
#Table(name = "parser", schema = "newsmonitoringdb")
public class ParserEntity {
public ParserEntity() {
}
public ParserEntity(String name, SourceTypesEntity type) {
this.name = name;
this.type = type;
}
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Basic
#Column(name = "name")
private String name;
#ManyToOne(optional = false)
#JoinColumn(name="type",referencedColumnName="id")
private SourceTypesEntity type;
...//getters, setters and other fields are here
}
}
Class ParserTypesEntity:
import javax.persistence.*;
import java.util.Collection;
import java.util.List;
/**
* Created by User on 17.08.2016.
*/
#Entity
#Table(name = "source_types", schema = "newsmonitoringdb")
public class ParserTypesEntity {
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Basic
#Column(name = "name")
private String name;
#OneToMany(mappedBy="type", fetch=FetchType.EAGER, targetEntity = ParserEntity.class)
private Collection<ParserEntity> siteParser;
...//getters and setters are here
}
}
They have a relation by field and everything seems ok.
However I would like a ParserTypeEntity class to save only names from ParserEntity class in a separate Collection field instead of ParserEntity list.
I would like them to be filled automatically when retrieving types from DB the same way it is done with ParserEntity objects now.
Is there any way to do that, or I have to change the relation to the unidirectional one, get all of the types and then get names for each type by its id?
They have a relation by field and everything seems ok. However I would
like a ParserTypeEntity class to save only names from ParserEntity
class in a separate Collection field instead of ParserEntity list.
I would like them to be filled automatically when retrieving types
from DB the same way it is done with ParserEntity objects now.
I don't think that you can use converter for relation with collections.
Besides, even if you could, I would not use it since it could make the code less natural and less readable. For an enum, it's useful and overall cosmetic but for entity collection, it looks like a hack since it breaks the ORM relationship between entities.
If you use JPA/EntityManager, I can propose a workaround : using a lifecycle event of entities. Otherwise, if you use Hibernate/SessionFactory, you should look listeners for Hibernate. It's the same principle.
The idea is keeping your actual mapping and adding a transient list with data you wish which would be populated in an automatic way when a ParserTypesEntity is loaded.
So, in ParserTypesEntity, you can add a hook method to do a processing when the entity is loaded. In this method, you could collect names of parserEntity collection and add it in a transient List<String>.
public class ParserTypesEntity {
...
#Transient
private List<String> parserTypeNames;
#PostLoad
public void postLoad() {
parserTypeNames = new ArrayList<>();
for (ParserEntity parserEntity : siteParser){
parserTypeNames.add(parserEntity.getName());
}
...
}
I want to store a List<String> in a postgres DB.
#ElementCollection
private List<String> products;
Hibernate will therefore create a join table. Is it possible to prevent this?
One workaround would be to create an explicit class with bidirectional mapping as follows:
#Entity
public class MainEntity {
#OneToMany(mappedBy = "main")
private List<Product> products;
}
#Entity
public class Product {
#Id
private long id;
#ManyToOne
private MainEntity main;
private String text;
}
But I feel this is a bit over the top for just storing a string list, isn't it?
If you don't find anything better, try this:
Mark your List with #Transient so that it is never persisted directly.
Prepare additional field for persisting your list, of a type that is "persistable" directly by JPA (concatenated, delimetered String seems to be quite natural).
Use methods annotated with #PostLoad and #PrePersist to move data between those two fields, converting from List to String and back.
i'm not sure but could you remove :
#ManyToOne
private MainEntity main;
in class product.
I think it might works properly without this.
Do you want to handle your list from MainEntity or from Product?
My simplified model looks like this:
#Entity public class Aspect extends Model {
#Id public Long id;
#OneToMany(cascade = CascadeType.ALL) public List<Restriction> restrictions;
}
#Entity public class Restriction extends Model {
#Id public Integer id;
#ManyToOne public RestrictionTemplate restrictionTemplate;
}
#Entity public class RestrictionTemplate extends Model {
#Id private Integer id;
}
Basically the idea is this: each Aspect has a set of Restrictions. And each Restriction itself relies on a RestrictionTemplate.
I want Aspect creation form to like this: user can select some RestrictionTemplates and on form submit new Restrictions should be created and associated with new Aspect.
Let me explain once again: On form submission I want to create Aspect and relating Restrictions based on RestrictionTemplate's ids provided.
Whicn names should the fields in the form have in order to make such binding possible?
The naming which works for direct relantionships:
restrictions[0].restrictionTemplate.id
restrictions[1].restrictionTemplate.id
doesn't work here (creates Aspect entry in DB, but no Restriction entries).
I think you simply must write a bit of code for that, in which you search for the RestrictionTemplates corresponding to the passed IDs and then assigning them to the new instance of Aspect:
List<RestrictionTemplates> templates = new ArrayList<RestrictionTemplates>();
for (int crtTplId : passedIds) {
templates.add(entityManager.find(RestrictionTemplates.class, crtTplId));
}
List<Restriction> restrictions = new ArrayList<Restriction>();
for (RestrictionTemplates crtTpl : templates) {
restrictions.add(new Restriction(crtTpl));
}
Aspect aspect = new Aspect();
aspect.restrictions = restrictions;
entityManager.persist(aspect);
PS: as I understand, there can be Restrictions that do not belong to an Aspect. If that is not true, than you should make your Aspect-Restriction relationship bilateral, Restriction being the owning side:
#Entity public class Aspect extends Model {
#Id public Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy="aspect") public List<Restriction> restrictions;
}
#Entity public class Restriction extends Model {
#Id public Integer id;
#OneToMany(/*+ add the #JoinColumn for marking the column not nullable*/) public Aspect aspect;
#ManyToOne public RestrictionTemplate restrictionTemplate;
}
I'm trying to use hibernate to map an object like this:
#Entity
public class ParentClass {
#Id
#GeneratedValue
int Id;
#OneToMany
Map<String, ChildClass> map;
}
#Entity
public class ChildClass {
#Id
#GeneratedValue
int Id;
String text;
}
I don't want Hibernate to create a join table. I want it to add a column to the table for ChildClass. I'd also prefer not to add a field for the key in ChildClass. Ideally, when saving the object Hibernate would automatically take the key in the map and save it in the corresponding table, and do the reverse when querying. Is this possible?
If I do have to add a field to ChildClass for the key, can hibernate populate this field automatically with the key from the map? The reason I ask is because I'm getting my data from a JSON web service and using Jackson to parse it and I don't know of a way to make Jackson copy the keys to the fields in the value objects. I could write code to do that manually, but I`d rather avoid that.
Unidirectional relation without jointable:
#Entity
public class Customer implements Serializable {
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="CUST_ID")
public Set<Ticket> getTickets() {
...
}
#Entity
public class Ticket implements Serializable {
... //no bidir
}
Bidirectional without jointable:
#Entity
public class Troop {
#OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne
#JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
It's from documentation: http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/
Apply these samples to your case.