Spring Data JDBC many to many relationship management - spring-data-jdbc

I have a many-to-many relationship person -> person_address <- address and use a reference class. But in my Person aggregate root it seems only adding person_address works (addresses collection):
#MappedCollection(idColumn = "PERSON_ID")
private Set<PersonAddress> addresses;
public void addAddress(final Address address) {
Assert.notNull(getId(),"Person ID cannot be null");
Assert.notNull(address.getId(),"Address ID cannot be null");
addresses.add(new PersonAddress(address.getId()));
}
I want to be able to delete from addresses collection and then do a save, but this doesn't work. So instead I use:
#Modifying
#Query(value = "delete from person_address where person_id = :personId and address_id = :addressId")
void deletePersonAddressById(#Param("personId") final Long personId, final Long addressId);
Is this the best way to handle this?
#ToString
#EqualsAndHashCode
#Getter
#Setter
public class PersonAddress {
/**
* Timestamp generated by database.
*/
#ReadOnlyProperty
#Setter(AccessLevel.NONE)
private LocalDateTime created;
private Long addressId;
public PersonAddress(final Long addressId) {
this.addressId = addressId;
}
}

You should be able to just remove entries from Person.addresses and save the entity again.
I created a sample to demonstrate this on GitHub.
On pitfall I fell into in the past was to not properly implement equals and hashCode for PersonAddress, which is of course important, when you want to remove instances from a HashSet or similar.

Related

spring JPA query to find events by locationId and categoryId

This is my events entity.
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Events {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long eventId;
#NotBlank(message = "Please Add Event name ")
#Length(max =100 ,min =2)
private String eventName ;
private String eventDescription;
// Each event is going to be mapped to a Location
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "location_id",
referencedColumnName = "locationId"
)
#NotNull
private Location location ;
#Temporal(TemporalType.DATE)
Date eventStartDate;
#Temporal(TemporalType.DATE)
Date eventEndDate;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "category_id",
referencedColumnName = "categoryId"
)
#NotNull
private Categories categories;
}
In my controller ,i have access to locationId and categoryId as request params .
I am not getting how to define my eventsRepository to access by locationId and categoryId. What changes should i make to this repo for things to work .
#Repository
public interface EventsRepository extends JpaRepository<Events,Long> {
public Events findByCateoryAndLocation()
}
I think a few adjustments you need to get rid of the issue. The query builder uses actual column names, so if your column name is locationId, then use 'findByLocationId(Integer locationId)' as a prototype. And please make sure entity names suit table names.
#Repository
public interface EventRepository extends JpaRepository<Event, Integer>
{
Event findByLocationIdAndCategoryId(Integer locationId, Integer categoryId);
}
This is off-topic, but I would like to mention that please do not use Lombok in entity classes. Getter, setter, and construction generators are ok, but hascode and string generators would be dangerous if you use lazy initialization. You may not get benefits from lazy loadings.
You have 2 ways to get your jpa-query working:
Modify your JPA-Query:
#Repository
public interface EventsRepository extends JpaRepository<Events,Long>
{
public Events findByCateories_IdAndLocation_id(Long categoriesId, long locationId)
}
Use a custom query - annotate your jpa with #Query and use a native query
There is one additional point from my side.
Naming of your classes. You are using plural which conflicts with the business logic - especially to the DB-relations(see Events to Categories). I would use singular (Event, Category)
This is exactly what I did to solve this with the help of native query.
#Query(
value = "SELECT * FROM events where category_id = ?1 AND location_id = ?2",
nativeQuery = true
)
public List<Events> findByCategoryIdAndLocationIdIn(Long CategoryId , Long LocationId);

One To One Mapping Spring Data JPA

I've a question about One to One unidirectional Mapping in Spring Boot.
I've a Customer class with a One to One unidirectional mapping to an Address class.
But when I try to associate a new customer with an existing Address, the database is updated.
So two Customers are now associated with the one Address.
As I understand it only one Customer should be associated with one unique Address. Do I understand the concept correctly, or am I doing something wrong in Spring Boot/ Spring Data JPA/ Hibernate?
Customer
#Entity
public class Customer {
#Id
private Long cId;
private String cName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="aid")
private Address cAddr;
:
}
Address
#Entity
public class Address {
#Id
private Long aid;
private String town;
private String county;
:
}
data.sql
insert into address values (100, "New York", "NY");
insert into customer values (1, "John Smith", 100);
Application.java
#Override
public void run(String... args) throws Exception {
Customer c1 = new Customer((long)5, "Mr. Men");
Optional<Address> a100 = ar.findById((long)100);
c1.setcAddr(a100.get());
cr.save(c1);
}
Database
There are 2 options on how to make #OneToOne relation: unidirectional and bidirectional: see hibernate doc.
When you scroll down a little bit you will find the following:
When using a bidirectional #OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side. If there are more than one children associated with the same parent, Hibernate will throw a org.hibernate.exception.ConstraintViolationException
It means that you'll have the exception only on fetching and when you have a bidirectional association. Because Hibernate will make an additional query to find the dependent entities, will find 2 of them, which doesn't fit #OneToOne relation and will have to throw an exception.
One way to "fix" uniqueness for your entities, is to make cAddr unique:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="aid", unique=true)
private Address cAddr;
If you create your db tables, by setting hbm2ddl property this will add a unique constraint to the aid column.
I really recommend to read the following:
#OneToOne javadoc itself provides examples of how to do everything correctly (for you Examples 1 and 2 are the most useful)
Check Vlad's blog about #OneToOne. It must be the best you can find. At least jump to the chapter "The most efficient mapping" and implement it bidirectional and sharing the PK, using #MapsId.
Also maybe you will come up to the idea to use #ManyToOne option (at least i can imagine that customer can have multiple addresses)
This is not One-to-Many relation. It's One-to-Many as One object has multiple related objects. Checkout this article.
Example:
Post.java
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table
public class Post {
#Id
#GeneratedValue
#Column(name = "post_id")
private Long id;
#Column
private String postHeader;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();
public void addComment(Comment comment) {
comments.add(comment);
}
public void removeComment(Comment comment) {
comments.remove(comment);
}
// equals() and hashCode()
}
Comment:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table
public class Comment {
#Id
#GeneratedValue
#Column(name = "postcom_id")
private Long id;
#Column
private String text;
// equals() and hashCode()
}
Check out step "3. Uni-directional one-to-one mapping demonstration" at this site basically carbon copy of what you're trying to do.

Spring JPA saving distinct entities with composite primary key not working as expected, updates same entity

I have a logic that saves some data and I use spring boot + spring data jpa.
Now, I have to save one object, and after moment, I have to save another objeect.
those of object consists of three primary key properties.
- partCode, setCode, itemCode.
let's say first object has a toString() returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0021, qty=1.0, sortNo=2, item=null)
and the second object has a toString returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0031, qty=1.0, sortNo=2, item=null)
there is a difference on itemCode value, and itemCode property is belonged to primary key, so the two objects are different each other.
but in my case, when I run the program, the webapp saves first object, and updates first object with second object value, not saving objects seperately.
(above image contains different values from this post question)
Here is my entity information:
/**
* The persistent class for the set_item database table.
*
*/
#Data
#DynamicInsert
#DynamicUpdate
#Entity
#ToString(includeFieldNames=true)
#Table(name="set_item")
#IdClass(SetGroupId.class)
public class SetItem extends BasicJpaModel<SetItemId> {
private static final long serialVersionUID = 1L;
#Id
#Column(name="PART_CODE")
private String partCode;
#Id
#Column(name="SET_CODE")
private String setCode;
#Id
#Column(name="ITEM_CODE")
private String itemCode;
private Double qty;
#Column(name="SORT_NO")
private int sortNo;
#Override
public SetItemId getId() {
if(BooleanUtils.ifNull(partCode, setCode, itemCode)){
return null;
}
return SetItemId.of(partCode, setCode, itemCode);
}
#ManyToMany(fetch=FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name="PART_CODE", referencedColumnName="PART_CODE", insertable=false, updatable=false)
, #JoinColumn(name="ITEM_CODE", referencedColumnName="ITEM_CODE", insertable=false, updatable=false)
})
private List<Item> item;
}
So the question is,
how do I save objects separately which the objects' composite primary keys are partially same amongst them.
EDIT:
The entity extends below class:
#Setter
#Getter
#MappedSuperclass
#DynamicInsert
#DynamicUpdate
public abstract class BasicJpaModel<PK extends Serializable> implements Persistable<PK>, Serializable {
#Override
#JsonIgnore
public boolean isNew() {
return null == getId();
}
}
EDIT again: embeddable class.
after soneone points out embeddable class, I noticed there are only just two properties, it should be three of it. thank you.
#Data
#NoArgsConstructor
#RequiredArgsConstructor(staticName="of")
#Embeddable
public class SetGroupId implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#NonNull
private String partCode;
#NonNull
private String setCode;
}
Check howto use #EmbeddedId & #Embeddable (update you might need to use AttributeOverrides in id field, not sure if Columns in #Embeddable works).
You could create class annotated #Embeddable and add all those three ID fields there.
#Embeddable
public class MyId {
private String partCode;
private String setCode;
private String itemCode;
}
Add needed getters & setters.
Then set in class SetItem this class to be the id like `#EmbeddedId´.
public class SetItem {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="partCode",
column=#Column(name="PART_CODE")),
#AttributeOverride(name="setCode",
column=#Column(name="SET_CODE"))
#AttributeOverride(name="itemCode",
column=#Column(name="ITEM_CODE"))
})
MyId id;
Check also Which annotation should I use: #IdClass or #EmbeddedId
Be sure to implement equals and hashCode in SetGroupId.
Can you provide that class?

How to correctly setup bi-directional one-to-many relationship in Hibernate

I have gone through several Q/As on stackoverflow and several other tutorials online to find what am I exactly missing for the problem described below.
Background:
I am learning to use restful APIs in my android application and for that reason, I have written a simple doctor-patient management app. There's a one to many relationship between a doctor and his patients. i.e. One doctor can have many patients.
Problem:
I am using one user table that is supposed to maintain all the user information, i.e. doctor and patients' basic info is maintained in this table and this table is also used for determining what type of user is trying to log in, so that appropriate screens can be presented. Here's how the entity for that table looks like:
#Entity
public class ConcreteUser implements User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name= "USER_ID")
private long id;
private String name;
private String email;
private int age;
private SEX sex;
private String accessLevel;
public ConcreteUser() {
}
// gettersand setters here
}
This Entity has one to one relationship with tables that maintain doctors and patient entities. And as mentioned earlier, doctors and patient entities have one to one relationship. Here's how those two entities look like:
#Entity
public class PatientEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PATIENT_RECORD_ID")
private long recordId;
// specify this as a foreign key from ConcreteUser entity
#OneToOne(cascade = CascadeType.ALL) /*CascadeType.ALL should not be required according to almost all the tutorials I have seen - But I always get unsaved transient object error if I don't do this and try to save a patient entity */
#JoinColumn(name="USER_ID")
private ConcreteUser patient;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "DOCTOR_RECORD_ID")
#JsonBackReference
private DoctorEntity doctor;
public PatientEntity() {
}
public void setDoctor(DoctorEntity doctor) {
this.doctor = doctor;
//if(!doctor.getPatients().contains(this)){
// doctor.addPatient(this);
//}
/* Commented out code always leads to stack overflow error */
/* although, according to tutorial in the link below, this code is necessary */
/* http://en.wikibooks.org/wiki/Java_Persistence/OneToMany */
}
// getters and setters are not shown
}
And lastly, here's my Doctor entity:
#Entity
public class DoctorEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "DOCTOR_RECORD_ID")
private long recordId;
// specify this as a foreign key from ConcreteUser entity
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="USER_ID")
private ConcreteUser doctor;
#OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL)
#JsonManagedReference
private Collection<PatientEntity> patients = new ArrayList<PatientEntity>();
public DoctorEntity() {
}
public boolean addPatient(PatientEntity p) {
boolean status = false;
status = patients.add(p);
//if (p.getDoctor() != this) {
// p.setDoctor(this);
//}
return status;
}
public boolean removePatient(PatientEntity p) {
boolean status = false;
status = patients.remove(p);
//if (p.getDoctor() != this) {
// p.setDoctor(null);
//}
return status;
}
// getters and setters are not shown. Same problem with the commented out code as described above
}
Now to test the fact that, when a POJO object of PatientEntity can be saved and it retains the information, I am using the following test case:
#Test
public void TestPatientDoctorManyToOne() throws Exception{
PatientEntity p1 = TestData.getPatientEntity(patient1);
DoctorEntity d = TestData.getDoctorEntity(doctor);
p1.setDoctor(d);
p1 = patientService.addPatient(p1);
assertNotNull(p1);
PatientEntity p2 = patientService.getPatientById(p1.getRecordId());
assertNotNull(p2);
assertNotNull(p2.getDoctor());
assertEquals(p1.getRecordId(), p2.getRecordId());
assertEquals(p1.getDoctor().getRecordId(), p2.getDoctor().getRecordId());
assertEquals(p1.getDoctor().getDoctor().getEmail(), p2.getDoctor().getDoctor().getEmail());
}
In the above test case, assertNotNull(p2.getDoctor()); assertion fails, as the returned patient entity does not contain doctor object at all.
Here's the log:
Outgoing:
"{"recordId":0,"patient":{"id":0,"name":"Patient-0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","email":"0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","age":50,"sex":"MALE","accessLevel":"patient"},"doctor":{"recordId":0,"doctor":{"id":0,"name":"Doctor-f025c8ce-8c31-4681-b673-a9e322dccf5a","email":"f025c8ce-8c31-4681-b673-a9e322dccf5a","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[]}}"
Incoming:
{"recordId":16,"patient":{"id":33,"name":"Patient-0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","email":"0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","age":50,"sex":"MALE","accessLevel":"patient"}}
As you can see, the returned object doesn't have a Doctor entity at all.
However, when I try to save the doctor entity with patients in it, it is saved with no problem. i.e. the following test case passes:
#Test
public void testDoctorPatientOneToMany() throws Exception {
PatientEntity p1 = TestData.getPatientEntity(patient1);
PatientEntity p2 = TestData.getPatientEntity(patient2);
DoctorEntity d = TestData.getDoctorEntity(doctor);
d.addPatient(p1);
d.addPatient(p2);
d = doctorService.addDoctor(d);
DoctorEntity d2 = doctorService.getDoctorById(d.getRecordId());
assertNotNull(d2);
assertEquals(d2.getRecordId(), d.getRecordId());
assertEquals(d2.getDoctor().getEmail(), d.getDoctor().getEmail());
}
Transactions for the above test case:
Outgoing:
"{"recordId":17,"doctor":{"id":43,"name":"Doctor-e4baeee7-eaaa-443e-8845-e0b12d7be82f","email":"e4baeee7-eaaa-443e-8845-e0b12d7be82f","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[{"recordId":21,"patient":{"id":44,"name":"Patient-d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","email":"d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","age":50,"sex":"MALE","accessLevel":"patient"}},{"recordId":22,"patient":{"id":45,"name":"Patient-5c9cfa3c-ee79-4aea-a193-4d8762f58431","email":"5c9cfa3c-ee79-4aea-a193-4d8762f58431","age":50,"sex":"MALE","accessLevel":"patient"}}]}[\r][\n]"
Incoming:
{"recordId":17,"doctor":{"id":43,"name":"Doctor-e4baeee7-eaaa-443e-8845-e0b12d7be82f","email":"e4baeee7-eaaa-443e-8845-e0b12d7be82f","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[{"recordId":21,"patient":{"id":44,"name":"Patient-d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","email":"d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","age":50,"sex":"MALE","accessLevel":"patient"}},{"recordId":22,"patient":{"id":45,"name":"Patient-5c9cfa3c-ee79-4aea-a193-4d8762f58431","email":"5c9cfa3c-ee79-4aea-a193-4d8762f58431","age":50,"sex":"MALE","accessLevel":"patient"}}]}
I apologize for along post, but I think I have exhausted all my resources. I'd absolutely worship anyone who decides to take a look at it and points out where the problem is. At this point, I am not even sure if I am testing this thing right, or my expectations are correct.
This is because by specifying mappedBy="doctor" in the DoctorEntity class
#Entity
public class DoctorEntity {
#OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL)
#JsonManagedReference
private Collection<PatientEntity> patients = new ArrayList<PatientEntity>();
public DoctorEntity() {
}
}
you are saying that DoctorEntity is no more the owner of the one-to-many relationship. PatientEntity is the owner. Hence during the save of PatientEntity (in the first test case) the foreign key of doctor entity is not updated in the Patient table.
mappedBy is equivalent to specifying inverse=true in an xml format.
Follow this link for a detailed explanation on what queries are executed when inverse=true or inverse=false is specified in the one-to-many mapping.

one-to-many bidirection performance issue

i have one doubt, that is when we create one-to-many as bidirectional. we will put one parent class reference in child class.
see the code.
Person.java
#Entity
#Table(name="PERSON")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="personId")
private int id;
#Column(name="personName")
private String name;
#OneToMany(cascade =CascadeType.ALL,fetch = FetchType.LAZY)
#JoinColumn(name="personId")
private Set <Address> addresses;
....
....
}
Address.java
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "addressId")
private int id;
#Column(name = "address",nullable=false)
private String address;
#ManyToOne(cascade =CascadeType.ALL)
#JoinColumn(name="personId",nullable=false)
private Person person;
....
....
}
here Person.java is parent class and Address.java is child class.
when we fetch the parent object from data base it is loading child class. it is fine. no proble.
but in vice versa ie. if we are fetching child class it is also holding parent class (person).
eg: we fetch Address by address id. just assume retrieved 100 address from data base.
but in address class person variable hold the parent(Person) object aslo.
my doubt is here
is use different 100 memory of person. with same data. in Address class. ?
my dao is like this.
public List<Address> getAllAddressByPersonId(int personId) {
List<Address> addressList = null;
try {
DetachedCriteria criteria = DetachedCriteria.forClass(Address.class);
criteria.createCriteria("person").add(Restrictions.eq("id", personId));
addressList = getHibernateTemplate().findByCriteria(criteria);
} catch (HibernateException e) {
e.printStackTrace();
} catch (DataAccessException e) {
e.printStackTrace();
}
return addressList;
just assume the size of addrssList is 100.
like
per = addressList.get(0).getPerson() ,
per 1 = addressList.get(1).getPerson(),
per2 = addressList.get(2).getPerson(),
....
per99 = addressList.get(99).getPerson().
here per1, per2 .... and per 99 are using same memory or different.
if same it is ok.. else it may cause of any performing issue of more memory utilization.?
help pls...
Hibernate does identity check. One hibernate session contains only single instance of an object with the same ID. So, if per, per1,...per99 is one person, you will have one object for it. Otherwise - different objects.

Resources