Insert to JPA collection without loading it - java

I'm currently using code like this to add a new entry to a set in my entity.
player = em.find(Player.class, playerId);
player.getAvatarAttributeOwnership().add(new AvatarAttributeOwnership(...));
It works, but every time I want to add one item, the whole set is loaded.
Is there a way (with a query maybe) to add the item without loading the rest? In SQL it would be something like INSERT INTO AvatarAttributeOwnership(player, data, ...) VALUES({player}, ...);
Currently uniqueness is maintained by the contract of Set and AvatarAttributeOwnership.equals, but I assume that won't work anymore. How can I enforce it anyway?
I'm using JPA2+Hibernate. Code:
#Entity
public class Player implements Serializable {
#Id
#GeneratedValue
private long id;
#ElementCollection(fetch=FetchType.LAZY)
// EDIT: answer to #2
#CollectionTable(uniqueConstraints=#UniqueConstraint(columnNames={"Player_id","gender","type","attrId"}))
Set<AvatarAttributeOwnership> ownedAvatarAttributes;
...
}
#Embeddable
public class AvatarAttributeOwnership implements Serializable {
#Column(nullable=false,length=6)
#Enumerated(EnumType.STRING)
private Gender gender;
#Column(nullable=false,length=20)
private String type;
#Column(nullable=false,length=50)
private String attrId;
#Column(nullable=false)
private Date since;
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
AvatarAttributeOwnership other = (AvatarAttributeOwnership) obj;
if (!attrId.equals(other.attrId)) return false;
if (gender != other.gender) return false;
if (!type.equals(other.type)) return false;
return true;
}
...
}

Try extra-lazy collections:
#LazyCollection(LazyCollectionOption.EXTRA)

Related

Spring JPA: How to get the custom json response

I need to list all the permission available in the system, and on each permission i need to show to particular user which permission is active on him ...
here is table relations:
All Record from Table: authority_master(select * from authority_master)
All Record from Table: users_authority_relation (select * from users_authority_relation;)
All Record from Table: userdetails (select*from userdetails)
Expected JSON Output : if i want to know which and all permission active on each user(basically whose record exists in table "user_authority_relation" table ,(here i want to list all the permissions available in table "authority_master" and on that "isActive" json key is True only if that particular authority exists in table "user_authority_relation"
Basically i need to select userdetails table and join with users_authority_relation , this will give result only who has permission But it will not list all the avaialble permission. i am confused on this how to get like below expected json result
If you are allowed to make multiple database call, you can do this
Get list of Authority authList from database.
Get list of users
For each user do the step 4-5
loop authList and check if the current user's authority list contains the element. if yes set isActive true otherwise false.
Set the authList as currents users permission
As i am also in spring boot learning phase and i found your problem interesting so i attempted to solve your problem
Your entity beans might look like this
user_authority_relation bean
#Table(name="user_authority_relation")
#Data
public class UserAuthRelation implements Serializable {
#Id
#GeneratedValue
Long user_auth_id;
#ManyToOne
#JoinColumn(name="userid")
UserDetails user;
#JoinColumn(name="authorityid")
#OneToOne
AuthorityMaster authority;
}
UserDetailsBean.java
#Entity
#Table(name="userdetails")
#Data
public class UserDetails implements Serializable {
#Id
#GeneratedValue
Long userid;
String name;
#OneToMany(mappedBy="user")
List<UserAuthRelation> userAuths;
}
AuthorityMaster.java
#Entity
#Table(name="authority_master")
#Data
public class AuthorityMaster implements Serializable {
#Id
#GeneratedValue
Long authorityid;
String authority;
String description;
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AuthorityMaster other = (AuthorityMaster) obj;
if (authorityid == null) {
if (other.authorityid != null)
return false;
} else if (!authorityid.equals(other.authorityid))
return false;
return true;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((authorityid == null) ? 0 : authorityid.hashCode());
return result;
}
}
To get desired response, we need to make some classes which can help to create custom response bean which may look like this
#Data
public class CustomResponseJson implements Serializable {
List<UserResponseJson> usersList;
}
#Data
public class UserResponseJson {
Long userId;
List<PermissionObject> permissions;
}
#Data
public class PermissionObject {
String authority;
Long authorityid;
Boolean isactive;
}
Rest Controller
#RestController
public class UsersController {
#Autowired
UserDetailRepository userRepo;
#Autowired
AuthorityRepository authRepo;
#GetMapping("/fetchUserPermissons")
#ResponseBody
public CustomResponseJson fetchUserPermission() {
//find all available users
List<UserDetails> users = userRepo.findAll();
//find all available authorities
List<AuthorityMaster> auths = authRepo.findAll();
//initilizing custom reponse bean
CustomResponseJson res =new CustomResponseJson();
if(users!=null && !users.isEmpty()){
//list of all users in json response
List<UserResponseJson> userJsonReponse = new ArrayList<UserResponseJson>();
for(UserDetails user : users){
UserResponseJson userjson = new UserResponseJson();
userjson.setUserId(user.getUserid());
//prepare list of all authority availed and not availed to user
List<PermissionObject> permissions = new ArrayList<PermissionObject>();
if(user.getUserAuths()!=null && user.getUserAuths().size()>0){
List<AuthorityMaster> tempList = new ArrayList<AuthorityMaster>();
for(UserAuthRelation rel : user.getUserAuths()){
tempList.add(rel.getAuthority());
PermissionObject permObj = new PermissionObject();
permObj.setAuthority(rel.getAuthority().getAuthority());
permObj.setAuthorityid(rel.getAuthority().getAuthorityid());
permObj.setIsactive(true);
permissions.add(permObj);
}
//to find authority which is not assigned to user
List<AuthorityMaster> remainedAuths = auths.stream()
.filter(e -> !tempList.contains(e))
.collect (Collectors.toList());
for(AuthorityMaster auth:remainedAuths){
PermissionObject permObj = new PermissionObject();
permObj.setAuthority(auth.getAuthority());
permObj.setAuthorityid(auth.getAuthorityid());
permObj.setIsactive(false);
permissions.add(permObj);
}
}
userjson.setPermissions(permissions);
userJsonReponse.add(userjson);
}
res.setUsersList(userJsonReponse);
}
return res;
}
}
OUTPUT
You should create one response DTO which can hold all data from different resources(databases, external services)
My Controller response is:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class AxeleStatisticReportDTO implements Serializable {
private static final long serialVersionUID = 1L;
private List<EmailLogDTO> emailAndCount = new ArrayList<>();
private List<IpLogDTO> ipRequestsCount = new ArrayList<>();
}

How to fix correct add to Set Hibernate Entity

I read an article about correct redefinition equals/hashCode:
https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
These overrides are performed in order not to lose the records already written to the Set.
Code:
#Entity
public class Client {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
public Client() {
}
public Client(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Client client = (Client) o;
return Objects.equals(id, client.id) &&
Objects.equals(name, client.name);
}
public int hashCode() {
return 31;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Client{");
sb.append("id=").append(id);
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
Then I test my class to make sure that it works correctly:
#Transactional
public class ClientTest {
#PersistenceContext
protected EntityManager em;
#Test
public void storeToSetBeforeMerge_ShouldBeContains() {
Set<Client> map = new HashSet<>();
Client client1 = new Client("John");
Client client2 = new Client("Mike");
map.add(client1);
map.add(client2);
Client merge1 = em.merge(client1);
Client merge2 = em.merge(client2);
assertTrue(map.contains(merge1)); // not true!
assertTrue(map.contains(merge2)); // not true!
}
}
My question is why conditions are not met. After all, I have indicated that the hashCode returns the same value: 31.
What am I doing wrong?
I can not understand the meaning of this decision. If this solution does not solve the problem, I cannot find the element I need from the Set
You did not call persist() before merge() as it is done in article. Author of the article explains it in first comment.
Merge is for integrating changes on detached entities, which have been
persisted previously.
Lifecycle of a new entity begins with persist(). Then merge() is called on detached entity with ID, condition will be met.
It's because HashSet is not only comparing results of hashCode. What it does is the following:
It compares the results of hashCode and if the results are different, then it returns true.
If results of hashCode are same, then it compares objects using equals and returns the result.
It's because of performance - calculating hashCode is faster and it is advised for the hashCode not to produce collisions very often.
Edit
In your equals method you're comparing using id, which is wrong as id is generated by database:
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Client client = (Client) o;
return Objects.equals(id, client.id) && // <- comparison by id
Objects.equals(name, client.name);
}
In your test you're creating the objects without id and put them in HashSet, then you're generating id and checking the Collection again:
#Test
public void storeToSetBeforeMerge_ShouldBeContains() {
Set<Client> map = new HashSet<>();
Client client1 = new Client("John");
Client client2 = new Client("Mike");
map.add(client1); // <- those don't have an id
map.add(client2);
Client merge1 = em.merge(client1); // those do have an id
Client merge2 = em.merge(client2);
assertTrue(map.contains(merge1)); // whose with id are not in set
assertTrue(map.contains(merge2));
}

Are multiple natural identifier combinations possible in Hibernate?

Please, consider the following example:
import org.hibernate.annotations.NaturalId;
import javax.persistence.Column;
import javax.persistence.Table;
#javax.persistence.Entity
#Table(name = "Entity", uniqueConstraints = {
#javax.persistence.UniqueConstraint(columnNames = {"firstNaturalId"}),
#javax.persistence.UniqueConstraint(columnNames = {"secondNaturalIdPart1", "secondNaturalIdPart2"})
})
class Entity {
#NaturalId
#Column(name = "firstNaturalId")
private String firstNaturalId;
#NaturalId
#Column(name = "secondNaturalIdPart1")
private String secondNaturalIdPart1;
#NaturalId
#Column(name = "secondNaturalIdPart2")
private String secondNaturalIdPart2;
// The remainder is omitted.
}
The desired functionality is to be able to retrieve an identified uniquely entity either by providing ('firstNaturalId') or the ('secondNaturalIdPart1', 'secondNaturalIdPart2') group.
Is it possible in Hibernate to have several natural identifiers combinations (groups) that uniquely identify an entity within a table?
A long time has passed. Maybe you've got your answer. I had this question in my mind. Found your question with no answer. Searched more. Found this Natural Id Mapping.
public class Entity{
#NaturalId #Embedded
private EntityNI entityNI;
public Entity(){}
// Setter and Getter is omitted
// .
// .
// .
#Override
public int hashCode(){
return (entityNI != null ? entityNI.hashCode() : 31);
}
#Override
public boolean equals(final Object object){
if(this == object){
return true;
}
if( !(object instanceof Entity) || (getClass() != object.getClass()) ){
return false;
}
return this.entityNI.equals( ((Entity)object).entityNI );
}
}
And the embedded natural ids:
#Embeddable
public class EntityNI{
#NotBlank #Basic(optional = false) #Column(name = "firstNaturalId")
private String firstNaturalId;
#NotBlank #Basic(optional = false) #Column(name = "secondNaturalIdPart1")
private String secondNaturalIdPart1;
#NotBlank #Basic(optional = false) #Column(name = "secondNaturalIdPart2")
private String secondNaturalIdPart2;
public EntityNI(){}
// Setters and Getters are omitted
// .
// .
// .
#Override
public int hashCode(){
return Objects.hash(firstNaturalId, secondNaturalIdPart1, secondNaturalIdPart2);
}
#Override
public boolean equals(Object object){
if(this == object){
return true;
}
if( !(object instanceof EntityNI) || (getClass() != object.getClass()) ){
return false;
}
final EntityNI other = (EntityNI) object;
return (this.firstNaturalId == other.firstNaturalId) && (this.secondNaturalIdPart1 == other.secondNaturalIdPart1) && (this.secondNaturalIdPart2 == other.secondNaturalIdPart2);
}
}

SELECT query with composite primary key

In a spring mvc app using hibernate and jpa, I recently switched to a composite primary key using an #Embeddable class. As a result, I need to update the JPA query that returns a given object based on its unique id. The following is the JPA code that used to work, but which no longer returns a result:
#SuppressWarnings("unchecked")
public Concept findConceptById(BigInteger id) {
Query query = this.em.createQuery("SELECT conc FROM Concept conc WHERE conc.id =:cid");
query.setParameter("cid", id);
return (Concept) query.getSingleResult();
}
How do I change the above query so that it returns the Concept with the most recent effectiveTime for the given id? Note that id and effectiveTime are the two properties of the ConceptPK composite primary key, and that thus the property definitions and getters and setters for id and effectiveTime are in the ConceptPK class and NOT in the Concept class.
The error thrown by the above is:
Caused by: java.lang.IllegalArgumentException:
Parameter value [786787679] did not match expected type [myapp.ConceptPK]
This is how the primary key is now defined in the Concept class:
private ConceptPK conceptPK;
And here is the code for the ConceptPK class:
#Embeddable
class ConceptPK implements Serializable {
#Column(name="id", nullable=false)
protected BigInteger id;
#Column(name="effectiveTime", nullable=false)
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime effectiveTime;
public ConceptPK() {}
public ConceptPK(BigInteger bint, DateTime dt) {
this.id = bint;
this.effectiveTime = dt;
}
/** getters and setters **/
public DateTime getEffectiveTime(){return effectiveTime;}
public void setEffectiveTime(DateTime ad){effectiveTime=ad;}
public void setId(BigInteger id) {this.id = id;}
public BigInteger getId() {return id;}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final ConceptPK other = (ConceptPK) obj;
if (effectiveTime == null) {
if (other.effectiveTime != null) return false;
} else if (!effectiveTime.equals(other.effectiveTime)) return false;
if (id == null) {
if (other.id != null) return false;
} else if (!id.equals(other.id)) return false;
return true;
}
#Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + ((effectiveTime == null) ? 0 : effectiveTime.hashCode());
hash = 53 * hash + ((id == null) ? 0 : id.hashCode());
return hash;
}
}
To use parts of composite primary key in JPA query, you have to address them using its variable names:
public Concept findConceptById(BigInteger id) {
Query query = this.em.createQuery("SELECT conc FROM Concept conc WHERE conc.conceptPK.id =:cid order by conc.conceptPK.effectiveTime desc");
query.setParameter("cid", id);
return (Concept) query.getSingleResult();
}
I used Concept as entity name assuming the class with #Entity annotation is also named Concept.
This question contains information about similar problem, you may find it useful.
Please try this
#SuppressWarnings("unchecked")
public Concept findConceptById(BigInteger id) {
Query query = this.em.createQuery("from Concept conc WHERE conc.conceptPK.id = :cid order by conc.conceptPK.effectiveTime desc");
query.setParameter("cid", id);
return (Concept) query.getSingleResult();
}
Make sure conceptPK has getter and setter methods in Concept class.

Understand OneToMany Relationship in JPA

I have two entities in in OneToMany Relationship:
The parent Entity:
#Entity
#Table(name = "PIANOTAGLIE")
public class PianoTaglia {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String pianoTaglia;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pianoTaglia", cascade = CascadeType.ALL)
private List<Taglia> taglie;
public PianoTaglia() {
}
[...] Getter/Setter [...]
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PianoTaglia other = (PianoTaglia) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
And Child entity:
#Entity
#Table(name = "TAGLIE")
public class Taglia {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 10, unique = true)
private String taglia;
#ManyToOne
private PianoTaglia pianoTaglia;
public Taglia() {
}
[...] Getter/Setter [...]
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Taglia other = (Taglia) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
For manage my Entities i use this generic Dao:
public abstract class JpaDAO<E> {
protected Class<E> entityClass;
#PersistenceContext(unitName = "PrudiPU")
protected EntityManager em;
#SuppressWarnings("unchecked")
public JpaDAO() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
this.entityClass = (Class<E>) genericSuperclass.getActualTypeArguments()[0];
}
public List<E> findAll() {
TypedQuery<E> q = em.createQuery("SELECT h FROM " + entityClass.getName() + " h", entityClass);
return q.getResultList();
}
public void persist(E entity) {
em.persist(entity);
}
public E getReference(Long id) {
return em.getReference(entityClass, id);
}
}
Specialized for each class (this is PianoTagliaDao but TagliaDao is the same)
#Repository
public class PianoTaglieDao extends JpaDAO<PianoTaglia> {
}
When I create a PianoTaglia I keep a reference to the object with the generated ID... So i can navigate through my application and at any time i can create a Taglia. When i create a Taglia i use the reference to PianoTaglia, previusly created, in this way:
PianoTaglia pt = getPreviuslyCreatedPianoTaglia(); //this is an example
Taglia tg = new Taglia();
tg.setTaglia("XXL");
tg.setPianoTaglia(pt);
pt.getTaglie().add(tg);
taglieDao.persist(tg);
taglieDao.flush(); //i need to flush for keep generated ID
[...]
If i check the tables into DB is all ok! All the tables are well populated! But if i try to get all PianoTaglia the taglie collections are always empty:
List<PianoTaglia> pianoTagle = pianoTagliaDao.findAll();
for(PianoTaglia pt : pianoTaglie) {
assert pt.getTaglie().isEmpty();
}
after testing i've found the solution: when i create taglia i have to keep a new reference of PianoTaglie:
PianoTaglia old = getPreviuslyCreatedPianoTaglia();
PianoTaglia pt = pianoTaglieDao.getReference(old.getId()); //getReference call the namesake method in the EntityManager
Taglia tg = new Taglia();
tg.setTaglia("XXL");
tg.setPianoTaglia(pt);
pt.getTaglie().add(tg);
taglieDao.persist(tg);
taglieDao.flush(); //i need to flush for keep generated ID
[...]
In this way when i keep the PianoTaglia Objects the taglie collections are well Populated..
My question is: Why JPA have this behaviour?
It looks like you are storing the previously created PianoTaglia and keeping it well after it's context has closed, so that it is considered unmanaged by the persistence unit. Unmanaged entities are not tracked, so any changes made are not reflected in the database. This means that the pt.getTaglie().add(tg); code isn't done on something that the entityManager is aware of.
By using the getReference or find api, you are retrieving the managed instance of the entity, so that any changes made to it are tracked by the EntityManager. You could also have replaced the getReference and pianoTaglieDao.persist(tg); line with a pianoTaglieDao.merge(old) call which will merge changes made to the old PianoTaglia back into the persistence unit. It is probably better though to use getReference or find rather than cache the unmanaged entity to help reduce overwriting with stale data. Your cached object might not reflect the latest changes made, which might then be overwriten by the merge call, and for performance, it will allow you to expand your app later on to multiple threads and servers without having to make drastic changes.
hi I am not sure what fields do you have in the db but try to add
#JoinColumn("user_id")
for the next line
#ManyToOne
private PianoTaglia pianoTaglia;
result will be
#ManyToOne
#JoinColumn("pianoTagliaId")
private PianoTaglia pianoTaglia;

Categories

Resources