relate entities by reference using spring boot, JPA - java

Say we have 2 classes Driver and Car with the Driver having a many-to-one relationship with the Car as follows.
#Entity
#Table(name = "driver")
public class Driver {
#Id #GeneratedValue
private Long id;
#ManyToOne
private Car car;
...
// getter setter ignored for brevity
}
Is there a way to set the value of car via post request for example by referencing car by its id by just JPA/Hibernate annotations? I'm still sort of new to Spring boot, so I was actually thinking of creating a new attribute Long carId and then apply #JsonIgnore to car, according to https://stackoverflow.com/a/42633336/9324939. Or is there any other suggestion or approach to get what I'm trying to achieve?
PS: In the database, they are already connected by reference.
-- in postgres
...
driver_id BIGINTEGER REFERENCES car (id)
...

please take a look here for a sample project I made to address this:
https://github.com/Fermi-4/StackOverflow---JPA-Relationships
Once started locally, use Postman to set the car to a driver:
http://localhost:9090/api/assigncar?driverId=1&carId=1
Driver Entity - using Lombok
#Entity
#Table(name = "driver")
#Data
public class Driver {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long driverId;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Car car;
}
Car Entity - using Lombok and #JsonIgnore to prevent infinite recursion
#Entity
#Table(name = "car")
#Data
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long carId;
#JsonIgnore
#OneToMany
private List<Driver> drivers = new ArrayList<Driver>();
}
Repositories
public interface CarRepository extends JpaRepository<Car, Long> { }
public interface DriverRepository extends JpaRepository<Driver, Long> { }
Controller Class
#RequestMapping("/api")
#RestController
public class DriverController {
#Autowired
CarRepository _carRepo;
#Autowired
DriverRepository _driverRepo;
#PostMapping("/assigncar")
public Driver assignCarToDriver(#RequestParam Long driverId,#RequestParam Long carId) {
Car car = _carRepo.findById(carId).get();
Driver driver = _driverRepo.findById(driverId).get();
driver.setCar(car);
return _driverRepo.save(driver);
}
}

When you add new driver via post request , you can assign a new car or an existing car within your json object (you can try to add cascadeType.ALL within your #ManyToOne)

Related

Spring Data JPA / Hibernate - findByY(String y) method in #Repository where Y is accessible via object X

I've got a UserRepository:
#Repository
public interface UserRepository extends JpaRepository<User, Long>
{
}
where User:
#Entity
#Table(name = 'user')
public class User
{
#Id
private Long id;
#OneToOne(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserDetails userDetails;
}
and UserDetails:
#Entity
#Table(name = 'user_details')
public class UserDetails
{
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User owner;
private String name;
}
Packages, imports, getters and setters are excluded for cleaner code.
Now, how can I find users by their name? Adding this to the UserRepository interface will not work:
List<User> findByName(String name);
because it throws:
No property name found for type User
I am looking for something like this:
List<User> findByNameOfUserDetails(String name);
Please take a look at the Spring Data JPA docs here.
You'll need something like findByUserDetailsName(String name).
To resolve this ambiguity you can use _ inside your method name to manually define traversal points. So our method name would be findByUserDetails_Name(String name).

JPA Bidirectional OneToOne Not working

I try to build a bidirectional relationship. I am using Spring Boot 1.5.4.RELEASE with Spring Boot JPA to generate my repositories. I try to save two entities which are associated to each other, but it isnt working. I commented the test-statements which fails.
My Entites:
Driver:
#Entity
#ToString
#EqualsAndHashCode
public class Driver {
public static final String COLUMN_CAR = "car";
#Id
#GeneratedValue
private long id;
#Getter
#Setter
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = COLUMN_CAR)
private Car car;
}
Car:
#Entity
#ToString
#EqualsAndHashCode
public class Car {
#Id
#GeneratedValue
private long id;
#Getter
#Setter
#OneToOne(mappedBy = Driver.COLUMN_CAR)
private Driver driver;
}
I used Spring JPA to generate repositories.
DriverRepository:
#Repository
public interface DriverRepository extends CrudRepository<Driver, Long> { }
CarRepository:
#Repository
public interface CarRepository extends CrudRepository<Car, Long> { }
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
#Transactional
public class StackoverflowTest {
#Autowired
private DriverRepository driverRepository;
#Autowired
private CarRepository carRepository;
#Test
public void test1() {
Driver driver = driverRepository.save(new Driver());
Car car = carRepository.save(new Car());
driver.setCar(car);
driverRepository.save(driver);
/* Success, so the driver got the car */
driverRepository.findAll().forEach(eachDriver -> Assert.assertNotNull(eachDriver.getCar()));
/* Fails, so the car doesnt got the driver */
carRepository.findAll().forEach(eachCar -> Assert.assertNotNull(eachCar.getDriver()));
}
#Test
public void test2() {
Driver driver = driverRepository.save(new Driver());
Car car = carRepository.save(new Car());
car.setDriver(driver);
carRepository.save(car);
/* Success, so the car got the driver */
carRepository.findAll().forEach(eachCar -> Assert.assertNotNull(eachCar.getDriver()));
/* Fails, so the driver doesnt got the car */
driverRepository.findAll().forEach(eachDriver -> Assert.assertNotNull(eachDriver.getCar()));
}
}
In both tests the last statement fails. Any ideas? Thanks in Advice.
Several mistakes in what you posted.
First:
#OneToOne(mappedBy = Driver.COLUMN_CAR)
mappedBy expects the name of the Java field/property on the other side of the association. Not the name of the database column. It works here because both happen to have the same name.
Second:
carRepository.findAll().forEach(eachCar -> Assert.assertNotNull(eachCar.getDriver()));
That fails simply because you're doing everything in a single transaction, and you failed to properly initialize the two sides of the association. So car.driver is just as you initialized it: null.
Third:
driverRepository.findAll().forEach(eachDriver -> Assert.assertNotNull(eachDriver.getCar()));
You made the same mistake as before, but worse. Here, you only initialized one side of the association, but you initialized the inverse side of the association (the one which has the mappedBy attribute). So the association won't even be saved in the database, as it would have been in your previous snippet.

JPA Annotations with inheritance

I am working on JPA project and I need your help.
I have two classes, “Person” and “Leader” which inherits from Person.
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(unique = true)
private String personId;
}
And
#Entity
public class Leader extends Person implements Serializable {
private List < Person > listTeam;
public void addPersonInTeam(Person e) {
listTeam.add(e);
}
}
My question Is, do I need to have JPA annotations #OneToMany or something else before private List listTeam in class Leader?
Thank you very much
You need to specify a mapping between the two classes because for Hibernate the association is not relevant here, you have to use annotations in both sides and I guess you will need a OneToMany mapping here :
Here's the mapping that you are seraching for:
In Person class:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(unique = true)
private String personId;
#ManyToOne
#JoinColumn(name="leader_id")
private Leader leader;
//getter and setter
}
In Leader class:
#Entity
public class Leader extends Person implements Serializable {
#OneToMany(mappedBy = "leader")
private List <Person> listTeam;
//getter and setter
public void addPersonInTeam(Person e) {
listTeam.add(e);
}
}
For further information you can see these links:
Hibernate – One-to-Many example (Annotation).
Hibernate One To Many Annotation tutorial.
Note:
I don't see the use of the field personId in the Person class, there's no need to use two differents ids.
EDIT:
To answer your questions:
The #JoinColumn(name="leader_id") is not mandatory, but it's used to specify the foreign key name.
If the relation is ManyToMany the mappedBy property is used to specify the owner of the relationship, you can see this answer for more details.

How to set relationship between tables Ebean/Play framework

I'm new to Ebean's world, and I encounter some difficulties to set some relationships between entities.
I have basically two classes, User and Car.
A user can have several cars (so I guess OneToMany) and a car can belongs to one User (so I guess OneToOne).
How can I link these two entities? Here it is what I've done so far
User
#Entity
public class User extends Model{
#Id
#GeneratedValue
public int id;
public String name;
#ManyToMany(cascade=CascadeType.ALL)
public List<Car> car = new ArrayList<Car>();
}
Car
#Entity
public class Car extends Model{
#Id
#GeneratedValue
public int id;
#OneToOne(cascade = CascadeType.ALL)
public User user;
}
And I get the following error
PersistenceException: Error on models.User.car Can not find mappedBy
property [users] in [models.Car]
Can someone explain me clearly how to use annotations the correct way (very poor documentation), and tell me why I get this error?
You guessed wrong :)
Your User should have a #OneToMany relationship with cars so:
#OneToMany(mappedBy = "user", cascade=CascadeType.ALL)
public List<Car> car = new ArrayList<Car>();
while your Car should have a #ManyToOne relationship :
#ManyToOne(cascade = CascadeType.ALL)
public User user;
Take care on the mappedBy property in the #OneToMany annotation: you need to tell Ebean where the foreign key lies in the related class.
User
#Entity
public class User extends Model{
#Id
#GeneratedValue
public int id;
public String name;
#OneToMany(cascade=CascadeType.ALL)
public List<Car> car = new ArrayList<Car>();
}
Car
#Entity
public class Car extends Model{
#Id
#GeneratedValue
public int id;
#ManyToOne(mappedBy="car") //will give you an error
public User user;
}
mappedBy here represents the owner of relation which is important in bidirectional relation.
Think in normal condition can a car exist without the User which owns it means User is the owner in a relation.So in your case User is the owner of relation.Mapped By
But the above code will not work The attribute mappedBy is undefined for the annotation type ManyToOne
In that case #JoinColumn come into picture.Join Column

Hibernate. Repeated column when mapping #IdClass annotated entity with composite key

I've ran into problem with composite primary key handling by Hibernate as a JPA provider.
My entities look like below
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#Column(name = "place_id")
private Integer placeId;
#Id
#Column(name = "external_object_id")
private Integer externalObjectId;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Integer placeId;
private Integer externalObjectId;
}
Looks pretty simple yet no matter what I do I keep getting the following exception (lines are splitted for readability):
org.hibernate.MappingException:
Repeated column in mapping for entity: ExternalMatch
column: external_object_id (should be mapped with insert="false" update="false")
I've tried placing annotation on entity class fields and key class fields together as well as separately, moving all annotations from fields to getters on each one of the classes, using key calss as #Embeddable and putting it into the entity class with #EmbeddedId. Nothing seems to work.
This case seems trivial so maybe it's something wrong with our setup but I can't even imagine where to look for the issue.
Any advice is much appreciated.
It appears that I shot myself in the foot with this.
The issue was that I had a biderectional mapping between ExternalMatch and ExternalObject I forgot about trying to replace the actual entity with its integer id.
So changing
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#Column(name = "place_id")
private Integer placeId;
#Id
#Column(name = "external_object_id")
private Integer externalObjectId;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Integer placeId;
private Integer externalObjectId;
}
// Related entity class
#Entity
#Table(name = "external_object")
public class ExternalObject extends AbstractNameableEntity {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "external_object_id", nullable = false)
private List<ExternalMatch> matches;
// ...
}
to reprsent actual mappings like this
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#ManyToOne
#JoinColumn(name = "external_object_id", referencedColumnName = "id")
private ExternalObject externalObject;
#Id
#ManyToOne
#JoinColumn(name = "place_id")
private Poi place;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Poi place;
private ExternalObject externalObject;
}
// Related entity class
#Entity
#Table(name = "external_object")
public class ExternalObject extends AbstractNameableEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "externalObject")
private List<ExternalMatch> matches;
// ...
}
resolved the repeated mapping issue yet leaving us with all the familiar troubles a biderectional mapping creates :)

Categories

Resources