#ContainedIn on field inside #EmbeddedId class - hibernate-search

My code has a Place and Address classes. Address 1:N Places.
There is one index, Address Index.
When the Address is saved, that is ok, works fine. But when the Place is saved, the Index of Address isn't updated.
The problem, maybe is a bug:
When #ContainedIn is on field inside #EmbeddedId class, that doesn't work. The intern mechanism isn't notified, hence index isn't changed.
I've tried some workarounds:
Updated index manually fullTextSession.index(obj). -> DON'T WORK
Mapped Address out of PlaceID class, with insertable and updatable as false. -> DON'T WORK
I've tried to study intern mechanism of HSearch, to try to resolve this problem inside a PreInsertEventListener/PreUpdateEventListener of Hibernate. -> Unsuccessfully.
So, I need to make this work in some way, then my main question is... How can I make it work?
For instance: Is there some intern mechanism/class of Hibernate Search? that I could use inside of a PreInsertEventListener/PreUpdateEventListener (manually way) or somethink like that...
Code of Place class:
#Entity
public class Place {
#IndexedEmbedded
#EmbeddedId
private PlaceID id;
#ContainedIn
#ManyToOne
#JoinColumns({
#JoinColumn(name = "address_id", insertable = false, updatable = false, referencedColumnName = "id"),
#JoinColumn(name = "address_secondId", insertable = false, updatable = false, referencedColumnName = "secondId")
})
private Address address;
#Field(store = Store.YES)
private String name;
}
Code of Address class:
#Indexed
#Entity
public class Address {
#FieldBridge(impl = AddressIdFieldBridge.class)
#IndexedEmbedded
#EmbeddedId
private AddressID id;
#Field(store = Store.YES)
private String street;
#Field(store = Store.YES)
private String city;
#IndexedEmbedded
#OneToMany(mappedBy = "id.address")
private Set<Place> places;
}
Versions of dependencies:
<hibernate.version>5.2.11.Final</hibernate.version>
<hibernate-search.version>5.8.0.Final</hibernate-search.version>
<lucene.version>5.5.4</lucene.version>
Code to update entity and index:
#Test
public void givenPlaces_updateAddressIndex(Address address) {
List<Place> places = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Place place = new Place(new PlaceID((long) new Random().nextInt(500), address));
place.setName("Place " + new Random().nextInt(500));
places.add(place);
}
placeRepository.save(places);
}
Result:

Updated index manually fullTextSession.index(obj). -> DON'T WORK
If that doesn't work, and you're passing the "address" object, it's not related to the #IndexedEmbedded/#ContainedIn.
Code to update entity and index:
This code doesn't update the places field in Address. Hibernate Search may be reindexing Address, but since the address currently in the Hibernate session has an empty places field, well...
Try adding the new places to Address.places in your code, it should work.

Related

Spring JPA Postgresql - Composite keys are duplicated when one is not

I am having some troubles and hope you can help me, I have the following entity:
App class:
#Entity
#Table(name = "apps")
public class App {
#Id
#Column(length = 15)
private String name;
#Column(length = 40)
private String web;
#Column(length = 50)
private String mailDomain;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "app")
private List<SocialNetwork> socialNetworks = new ArrayList<>();
//getters, setters, equals and hash
Social Network Class:
#Entity
#Table(name = "social_networks")
#IdClass(SocialNetworkCompositeKey.class)
public class SocialNetwork {
#Id
#Column(length = 15)
private String name;
#Id
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "app")
private App app;
#Column(nullable = false, length = 40)
private String url;
//getters, setters, equals and hash
SocialNetworkCompositeKey Class:
public class SocialNetworkCompositeKey implements Serializable {
private String name;
private String app;
//equals and hash
Now whenever I try to insert an App either in my Program or directly in the DB, I get the Exception:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "uk_590itpuvpqppd9f0g2w5y8bml"
Detail: Key (app)=(Uli App) already exists.
This while trying to insert 2 records with:
app name url
----+---------+-----------+---------
1 | Uli App | Twitter | http...
----+---------+-----------+---------
2 | Uli App | Linkedin | http...
----+---------+-----------+---------
I am using the latest version of both Spring boot and JPA. So I use JpaRepository for my repositories. Even if I try to enter those rows manually in the DB with pgAdmin it'll give me the same error.
I am not sure if it's related but I use ddl-auto: update from hibernate to auto create the tables.
I hope you guys can help me, cheers.
as #Morteza said my relationships were wrong. With this and other tutorial I found after digging alot (I really Googled alot before posting this) I was able to fix it by changing the relationship from #OneToMany to #ManyToOne in the Social Networks class. These are changes I've made:
App class:
#OneToMany(mappedBy = "app", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<SocialNetwork> socialNetworks = new ArrayList<>();
Social Network class:
#Id
#ManyToOne(fetch = FetchType.LAZY)
private App app;
Thanks for the help guys!

How to show 2 different type of mappings between 2 entity classes, holding each other's reference

The mappings between the 2 tables(Department and Employee) is as follows (Link for the image showing mapping is also provided):
Every department has one and only one department head.
Every department can have more than one employee.
dept_id and empId are primary keys of their respective tables.
dept_head(It is the Employee Id) and dept are foreign keys of their
respective tables.
Mapping Employee and Department table
I created entity classes for the above 2 tables (The structure is provided below).
Employee Class:
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "empId")
private Integer empId;
#Size(max = 45)
#Column(name = "name")
private String name;
#Size(max = 45)
#Column(name = "address")
private String address;
#Size(max = 45)
#Column(name = "grade")
private String grade;
#Size(max = 45)
#Column(name = "email")
private String email;
#JoinColumn(name = "dept", referencedColumnName = "dept_id")
#ManyToOne
private Department deptartment;
.. ...
}
Department class:
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 8)
#Column(name = "dept_id")
private String deptId;
#Size(max = 45)
#Column(name = "name")
private String name;
#JoinColumn(name = "dept_head", referencedColumnName = "empId")
#OneToOne
private Employee deptHead;
#OneToMany(mappedBy = "deptartment")
private List<Employee> employeeList;
....
...
}
If I am adding mappedBy in Employee Class (like I did in Department), to show OneToOne mapping between empId and deptHead,the code is compiling and running. However, If I do not add the mappedBy statement in Employee class, as the above code shows, the code still compiles and runs fine.
I would want to know why the code above works even if I am not providing mappedBy in employee class.
If anybody can help me clearing the above doubts and explaining the logic behind its working would be great as I am new to this.
It is not quite clear where you tried to user it with and without the mappedBy attribute.
But if I get your question correctly, you ask why you can have only one or both sides annotated?
It depends on which side is the source and destination of your relation or wheter it's bi-directional. On the Java-side you can have a relation always in both directions due to object references, but on the Database-side, you might only have it in one direction.
Check out JPA Wiki book on that topic for more details.
Additionally, the API doc for OneToOne states:
Specifies a single-valued association to another entity that has
one-to-one multiplicity. It is not normally necessary to specify the
associated target entity explicitly since it can usually be inferred
from the type of the object being referenced. If the relationship is
bidirectional, the non-owning side must use the mappedBy element of
the OneToOne annotation to specify the relationship field or property
of the owning side.

How to select an attribut that is on the "wrong" side of a OneToOne unilateral relationship with JPA criteria

I've 2 entities:
#Entity
public class Customer{
#Id
#Column(name = "ID")
private Long id;
#OneToOne(mappedBy = "customer")
private Address address;
#Column(name = "FIELD_1")
private String field1;
#Column(name = "FIELD_2")
private String field2;
}
#Entity
public class Address{
#Id
#Column(name = "ID")
private Long id;
#OneToOne
#JoinColumn(name = "CUST_ID")
private Customer customer;
}
If I do a select to retrieve the Customer, the Adress is retrieved as well, all good.
But I want to do a projection and build a DTO, so only select field1 and field2 along with the Adress entity.
public class MyDTO {
private String field1;
private String field2;
private Address address;
public MyDTO (String pField1, String pField2, Adress pAddress){
field1 = pField1;
field2 = pField2;
adress = pAddress;
}
}
So I've coded that DAO method :
public List<MyDTO > getListMyDTO() {
CriteriaQuery<MyDTO > crit = builder.createQuery(MyDTO .class);
Root<Customer> root = crit.from(Customer.class);
// This is to avoid a inner join since some customer may not have an adress and i dont want to exclude them from my select
root.join("address", JoinType.LEFT);
crit.multiselect(root.get("field1"), root.get("field2"), root.get("address"));
return em.createQuery(crit).getResultList();
}
em being the entity manager.
However that doesn't work and the address ends up always null.
I sorta understand this as there is no field from root pointing to the address table, but since it works when i do a select on the entity instead of a DTO, there must be a way to make this work no?

QueryDSL / JPQL : how to build a join query?

I've tried to read through the QueryDSL docs but I am still very confused. I'm accustomed to writing a lot of SQL, but this is my first real crack at using QueryDSL w/ JPQL (JPA2).
I have the following entity:
#Entity
public class Provider implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
private String name;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "provider_contact", joinColumns = #JoinColumn(name = "contact_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "provider_id", referencedColumnName = "id"))
#OrderColumn
private Collection<Contact> contact;
}
where Contact is a simple entity with an id for a pk.
#Entity
public class Contact {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
/**
* User first name
*/
#NotNull
private String firstName;
/**
* User last name
*/
#NotNull
private String lastName;
}
I'm trying to write a query which returns a Contact object given a specific Contact.id and Provider.id. If the Contact object is not a part of the Provider's Contact collection, I'm looking for a null value.
I've tried the following:
public Contact getContact( long providerId, long contactId ){
Predicate p = QProvider.provider.id.eq(providerId).and(QContact.contact.id.eq(contactId));
JPQLQuery query = new JPAQuery(em);
return query.from(QProvider.provider).innerJoin(QProvider.provider.contact).where(p).singleResult(QContact.contact);
}
but I'm getting the following error:
Caused by: java.lang.IllegalArgumentException: Undeclared path 'contact'. Add this path as a source to the query to be able to reference it.
at com.mysema.query.types.ValidatingVisitor.visit(ValidatingVisitor.java:78)
at com.mysema.query.types.ValidatingVisitor.visit(ValidatingVisitor.java:30)
at com.mysema.query.types.PathImpl.accept(PathImpl.java:94)
I'm presuming it has something to do with the fact that my predicate references QContact.contact direction and not part of the QProvider.provider.contact object, but I'm really at a loss as to figure out how this should be done.
Am I even on the right track? I'm not even sure my join is correct either.
This should work
public Contact getContact(long providerId, long contactId) {
QProvider provider = QProvider.provider;
QContact contact = QContact.contact;
return new JPAQuery(em).from(provider)
.innerJoin(provider.contact, contact)
.where(provider.id.eq(providerId), contact.id.eq(contactId))
.singleResult(contact);
}

GWT Resolver attribute of relationship can not be resolved

I'm working on a GXT project using JPA for persistence, but I'm facing an issue with bidirectionnal relationship persistence.
I have those two Entities :
#Entity
#Table(name = "ACTV_REQ", catalog = "erpdb")
#AttributeOverride(name = "id", column = #Column(name = "ID", nullable = false, columnDefinition = "BIGINT UNSIGNED"))
#NamedQueries(value = {
#NamedQuery(name = "findByPerson", query="select object(m) from ActvReq m where m.people= :people")
})
public class ActvReq extends BaseEntity {
#ManyToOne
#JoinColumn(name = "PPL_ID")
#NotNull
private People people;
#ManyToOne
#JoinColumn(name = "ACTV_TYP_ID")
private ActivityTyp actvTyp;
#ManyToOne
#JoinColumn(name= "PPL_ACTV_RIGHT_ID")
private PeopleActvRight pplActvRight;
#Column(name = "DESCR")
private String desc;
}
And :
#Entity
#Table(name = "PPL_ACTV_RIGHT", catalog = "erpdb")
#AttributeOverride(name = "id", column = #Column(name = "ID", nullable = false, columnDefinition = "BIGINT UNSIGNED"))
#PeopleActvRightBeanConstraint
#NamedQueries(value = {
#NamedQuery(name = "findByPeople", query="select object(m) from PeopleActvRight m where m.people= :people")
})
public class PeopleActvRight extends BaseEntity {
#ManyToOne
#JoinColumn(name="ACTV_TYP_ID")
#NotNull
ActivityTyp type;
#ManyToOne
#JoinColumn(name="PPL_ID")
#NotNull
People people;
#ManyToOne
#JoinColumn(name="ACTV_RIGHT_ID")
ActvRight actvRight;
#OneToMany(mappedBy="pplActvRight",cascade=CascadeType.ALL)
private List<ActvReq> actvRequests = new ArrayList<ActvReq>();
}
(I did not copy getters and setters but thoses methods exists.)
For the persistence of ActvReqProxy, it's basically done that way in my EditorPresenter :
getRequestContext().persistAndReturn(getModel()).with("actvTyp","people","pplActvRight").fire(new Receiver<M>() {
#Override
public void onSuccess(M response) {
unsetContext();
onSaveSuccess( response );
}
});
And the response pplActvRight is already null in the response I get, but in getModel() pplActvReqProxy is set.
On server side I've a service which calls the following method of my DAO :
public ActvReq persistAndReturn(ActvReq entity){
em.getTransaction().begin();
em.persist(entity);
em.close;
return entity;
}
And when I'm trying to persist a ActvReqProxy from my editor, using method with("pplActvRight","people",actvType"), I don't get any errors, but in my DB the entity is not entirely persisted. I mean a new ActvReq is created in the DB but field PPL_ACTV_RIGHT_ID remains null. (It works fine for people and actvTyp)
EDIT : In fact I assume the problem is located on GWT Resolver in resolveDomainValue, it can not resolve the attribute pplActvRight. It's as if my EntityProxy object doesn't exists on server-side.
Any ideas?
So at the beginning of persistAndReturn on server side it is already null? If so, then at least we know that it has nothing to do with JPA.
And you're sure that on client side it is set to something other than null on proxy before calling persistAndReturn? You can easily verify it: using Eclipse debugger it is possible to see JSON code to which proxy will be serialized (one of fields of proxy that you can see when you select proxy object in debugger). Please make sure that pplActvRight key is there with not-null value.
If so, maybe you should debug GWT source code that translates proxies to server-side entities to check what is being done with that pplActvRight property (why isn't it assigned to corresponding ActvReq server side instance). I can't remember what the class name doing this stuff was but if you won't be able to find it I can search it for you.