I have two domain class City and School with OneToMany.
#Entity
public class City {
...
#OneToMany(mappedBy="city")
private Set<School> schools = new HashSet<>();
...
}
#Entity
public class School {
...
#ManyToOne
private City city;
...
}
The corresponding repository are:
#Repository
public interface CityRepository extends JpaRepository<City, Long>{
}
#Repository
public interface SchoolRepository extends JpaRepository<School, Long> {
}
In the CityController's method:
#GetMapping(....)
public ResponseEntity<City> getSchool(#PathVariable Long id) {
City city = cityRepository.findOne(id);
return ResponseUtil.wrapOrNotFound(Optional.ofNullable(city));
}
When I debug to check schools member of the city object, there is an exception:
Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
Don't know why, I just want to get the city with all schools in that city (but don't want to add some annotation to domain class, I prefer to implement it in #Repository, better using #Query). Appreciated for any help.
In Hibernate, all relationships have FetchType.LAZY by default. You need to set it to eager to get the city.
#ManyToOne(fetch = FetchType.EAGER)
private City city;
Related
So I have the following application, which has one entity called Product that inherits two other entities (Product > Bike, Product > Wheel). Controllers look like this:
public abstract class ProductController<T extends Product> {
#Autowired
private ProductService<T> productService;
#PostMapping
public ResponseEntity post(#RequestBody T product) {
return ResponseEntity.ok(productService.save(product));
}
#GetMapping
public ResponseEntity get() {
return ResponseEntity.ok(productService.findAll());
}
}
#RestController
#RequestMapping("/products/categories/bikes")
public class BikeController extends ProductController<Bike> {}
#RestController
#RequestMapping("/products/categories/wheels")
public class WheelController extends ProductController<Wheel> {}
Entities:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Product() {}
//getters and setters
}
#Entity
public class Bike extends Product {
private String color;
public Bike() {}
//getters and setters
}
#Entity
public class Wheel extends Product {
private Double diameter;
public Wheel() {}
//getters and setters
}
Product repository:
public interface ProductRepository<T extends Product> extends CrudRepository<T, Long> {}
Product service:
#Service
public class ProductService<T extends Product> {
#Autowired
private ProductRepository<T> productRepository;
public T save(T product) {
return productRepository.save(product);
}
public Iterable<T> findAll() { return productRepository.findAll(); }
}
After running the program it creates three tables for each entity. If i call the post method from the concrete controller it adds the entity to its table. But the findAll() method returns entities not from the concrete table but from the product table. So my question is why is this happening (even if i specified the Entity type in the repository)?
What you want to achive is impossible because Bike is a subtype of Product in java but not for JPA. In the Database it usally split into different Tables for each Subtype and only the tables (seen by JPA as entites) can be queried. So a Spring Repository can't query it either, because it uses JPA internally.
Please take a look here and read more about jpa inheritance: https://www.baeldung.com/hibernate-inheritance
Generally: It is bad pratice to have different table for each Type of Product. Everytime you add a new Product you would have to add a whole new class and table in database. It is best practice to just have a single entity/class (and thus a single table) for Product and a field to distinguish between different types of products. In it's simplest form a String productType, but i would recommand using a seperate Entity ProductType with an id and name (in its simplest form) and have a #OneToMany relation between Product and ProductType. Additonal data like color and diameter could be stored using a field of type Map with #ElementCollection or in a another entity for (dynamic) additional data (as seen here: https://stackoverflow.com/a/15061468/7142748).
In my Spring boot batch application, I am calling a JPA repository class from Tasklet.
The JPA call retrieves a particular value (Entity object) from DB. The problem is, If I update some value in the entity object, once the control goes out of Tasklet, it automatically updates to DB even though I am not calling any save operation. How to prevent this? Default JPA implementation is Hibernate.
Tasklet class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
addressList.forEach(e -> e.setStatus(Status.INVALID.toString()));
Repository
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#Query("select em from Employee em where em.employeeName = :employeeName")
public Employee fetchEmployee(#Param("employeeName") Long employeeName);
}
Entity class
#Entity
#Table(name = "Employee")
public class Employee implements java.io.Serializable {
private static final long serialVersionUID = -3769636546619492649L;
private Long id;
private List<Address> address;
private String employeeName;
// Getters and setters
// #OneToMany mapping to Address
}
Even though I am not calling a .save() operation, it automatically updates Address table Status to "INVALID"
This happen because the entity is not in detached state. In EJB we can do this in the following way.
EJB solution
#Query(value = "select * from Employee WHERE EmployeeName = ?1", nativeQuery = true)
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<Employee> fetchEmployee(String employeeName);
This will make the transaction closed. Changes you make to entity will not get saved in DB
Spring JPA
After a bit of research i found JPA doesn't provide the detach functionality out of the box.
Refer : https://github.com/spring-projects/spring-data-jpa/issues/641
To make it work we can have a custom JPA repository which overrides detach method. An example is given in this link.
https://www.javaer101.com/en/article/1428895.html
Use Deep cloning to solve your issue.
First override the clone method inside your Address class like below.
Note : Please customize the implementation of clone() method by adding your class attributes.Since you didn't mention the structure of the class Address , I have implemented the solution with my own defined class attributes.
Address class
public class Address {
private String country;
private String city;
private String district;
private String addressValue;
public Address() {
super();
}
public Address(String country, String city, String district, String addressValue) {
super();
this.country = country;
this.city = city;
this.district = district;
this.addressValue = addressValue;
}
//Getters and Setters
#Override
protected Object clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
return new Address(this.getCountry(), this.getCity(), this.getDistrict(),this.getAddressValue());
}
}
}
Then re construct your class Tasket like below.
Tasket Class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
List<Address> clonedAddressList = new ArrayList<>();
addressList.forEach(address -> clonedAddressList.add((Address)address.clone()) );
clonedAddressList.forEach(address -> address.setStatus(Status.INVALID.toString()));
Let's assume there is a class named Person with following structure in spring boot
#Entity
class Person {
Long id;
String name;
#OneToMany
Set<PhoneNumber> phoneNumbers;
}
Person consists of set of phone numbers.
#Entity
class PhoneNumber {
Long id;
#ManyToOne
#JoinByColumn("person_id")
Person person;
String category;
String mobileNumber;
String phoneNumber;
}
PhoneNumber is a class which consists of above fields where category represents mobile or phone etc.
class PersonRepository extends JPARepository<Person, Long> {
Person findById(Long id);
}
So, whenever I want to fetch Person details with some id, I will call the above method findById , then it should fetch Person details along with phoneNumbers whose category is mobile.
The approach should be whenever it executes query internally for the findById method, it should execute subsequent query for fetching PhoneNumber whose category is mobile.
Is there any way I can get it as mentioned above or is there any other approach for achieving it? Please let me know.
PS: If there are any issues or errors in my way of asking please comment below. It will help me.
You can get it. Refer this:
Repo:
class PersonRepository extends JPARepository<Person, Long>,JpaSpecificationExecutor<Post> {
Person findById(Long id);
}
public static Specification<Person> search(Long id) {
return ((root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
return criteriaBuilder.and(
criteriaBuilder.equal(root.get("id"), id),
criteriaBuilder.equal(root.join("phone_number").get("category"), "mobile")
);
});
}
personRepo.findAll(search(10));
You can try this method-
// method in Person entity class
public static List<Person> findByIdMobile(long id, String category) {
return find("id = ?1 and phoneNumbers.category = ?2", id, category).list();
}
// can use this as
List<Person> mobilePersons = Person.findByIdMobile(1234,"mobile");
So i have following Entitys/Tables for a many to many relation: Satz, Track and the mapping Table Trackliste
#Entity
class Track{
// name, id
#ManyToMany(targetEntity = Satz.class, fetch = FetchType.LAZY)
#JoinTable(
name="trackliste", joinColumns=#JoinColumn(name="TrackID"),
inverseJoinColumns=#JoinColumn(name="SatzID"))
private Set<Satz> saetze;
// getters and setters
}
#Entity
class Trackliste {
// id, trackid, satzid.
// getters and setters
}
#Entity
public class Satz implements Serializable {
// id, titel, werkId, etc
#ManyToMany(mappedBy="saetze", fetch = FetchType.LAZY)
private Set<Track> tracks;
// getters and setters
}
and my repository looks like this:
public interface SatzRepository extends CrudRepository<Satz, Integer> {
List<Satz> findById(int id);
List<Satz> findByWerkId(int id);
//Some query maybe?
//List<Satz> findByTracks(String name);?
}
The Mapping works so far, when i call my Webservice it returns a json object and with the help of debugging i can see that the SatzRepository Set Contains objects of Track.
Now comes my question: How do i return a Satz based on the given Track name is this possible? Lets say i have a URL like this: localhost:8080/rest/satz/trackname?name=%trackname%
If you need more Information please tell me.
you can add a method in TrackRepository to find track by name then you can get satz list from track object.
public interface TrackRepository extends CrudRepository<Track, Integer> {
Track findByName(String name);
}
#Transactional
public TrackServiceImpl implement TrackService{
#AutoWired
TrackRepository trackRepository;
List<Satz> getSatzByTrackName(String name){
Track track = trackRepository.getByName(name);
return track != null ? track.getSaetze() : new ArrayList<>();
}
}
Bear with me for any mistakes I make, as this is my first question here.
I have a database with two tables, one table called: PERSON
with the following entity:
#Entity
class Person {
#Id
private String guid;
private String firstName;
private String organisationGuid;
...
...
}
And one table called: ORGANISATION
with the following entity:
#Entity
class Organisation {
#Id
private String guid;
private String name;
...
...
}
As you can see, every Person belongs to an Organisation.
And now I need to list all my persons, with the name of the organisation. I do not want the full Organisation-entity on the Person-entity, rather just the name. Like so:
[{
"guid": "xxx",
"firstName": "Name",
"organisationGuid": "yyy",
"organisationName": "Name of yyy"
}]
How can I accomplish this in the easiest way possible?
Things I have already tried:
1) Adding property to Person and modyfing select-statement
#Entity
class Person {
#Id
private String guid;
private String firstName;
private String organisationGuid;
private String organisationName;
...
...
}
--
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Query(nativeQuery = true, value = "select p.*, o.name as organisation_name from person p left join organisation o on p.organisation_guid = o.guid")
List<Person> findAll();
}
Result: This works fine when using findAll but as soon as I try to save a Person I get an error stating that column ORGANISATION_NAME does not exist.
2) OK, makes sense, so I tried to put #Transient on the field in the entity
#Entity
class Person {
...
#Transient
private String organisationName;
...
...
}
Result: Now it works to save the entity, but I never get the organisationName (as it is marked as Transient).
3) Well damn, then I try to use the annotation #ReadOnlyProperty
#Entity
class Person {
...
#ReadOnlyProperty
private String organisationName;
...
...
}
Result: Same error as in (1). I can findAll but as soon as I try to save a person entity hibernate reports that the column does not exist (because in order to save an item, hibernate first needs to select it, and this particular select does NOT use my own custom select I created in the repository).
4) So then I created a class called PersonOrganisation (with #Table(name="organisation")) with a #ManyToOne-relation from Person to PersonOrganisation, where PersonOrganisation is an entity with just two fields, guid and name.
Result: Error. I can findAll but as soon as I try to save a person entity hibernate reports that the organisationGuid does not match a PersonOrganisation in the database (as it seems that PersonOrganisation is not an Organisation the way Hibernate sees it).
Many things that can be improved here:
1) Add a relationship in the Person relating to the Organization:
#Entity
class Person {
#Id
private String guid;
private String firstName;
#ManyToOne
#JoinColumn(name = "organisationGuid")
private Organisation organisation;
2) create a Result Class which would be holding the projection results:
package com.mypkg;
#Entity
class PersonOrganization {
private String guid;
private String firstName;
private String organisationGuid;
private String organisationName;
public PersonOrganization(String guid, String firstName
, String organisationGuid, String organisationName){
// set the fields
}
}
3) Change the query (dont use native.. its not necessary):
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Query("select NEW com.mypkg.PersonOrganization(p.guid as guid ...
, o.name as organisationName)
from person p left join p.organisation o")
List<PersonOrganization> findPersonWithOrganization();
}
Remeber to add aliases to each result column to match the consturctor of the PersonOrganization class.
I started using the solution from Maciej above, but ran into problems when I didn't want to create an extra "projection" entity with the same fields as the original entity (there were 20 additional fields the on Person entity).
So I actually found another solution which I am very happy with.
1) I started out with adding a #ManyToOne in Person (like Maciej suggested)
#Entity
public class Person {
#Id
private String guid;
private String firstName;
#ManyToOne
private Organisation organisation;
...
...
}
2) I also added a custom serializer to the get-method for the Organisation on the Person entity:
#JsonSerialize(using = OrganisationLightSerializer.class)
public Organisation getOrganisation() {
return organisation;
}
The custom serializer is super simple:
public class OrganisationLightSerializer extends JsonSerializer<Organisation> {
#Override
public void serialize(Organisation organisation, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", organisation.getName());
jsonGenerator.writeEndObject();
}
}
3) Then I changed all find-queries in my repository and added join fetch, and with this I stopped hibernate from using (at least) two SQL-queries when fetching a list of Persons:
#Repository
public interface PersonRepository extends CrudRepository<Person, String> {
#Override
#Query("select p from Person p join fetch p.organisation o")
List<Person> findAll();
}
Result: I did not get the JSON-structure exactly the way I wanted it, but I managed to get just the pertinent information from each Organisation on each Person. The resulting JSON when fetching persons looks like so (as you can see I skipped the guid of the Organisation in the end, but it can easily be added again by just changing the custom serializer):
[{
"guid": "xxx",
"firstName": "Name",
"organisation": { name: "Name of yyy"}
}]
Disclaimer: Now I realize that my response here is not an exact answer to my own query, as I stated I wanted the JSON as a flat structure, but having an Organisation object in the JSON, which only contains the name of the Organisation is almost as good a solution. Should I edit/change/comment the question so that it reflects the actual answer here, or is this minor change an "acceptable" deviation of the requirements.