Spring Data Jpa OneToMany Saving New Child To Persisted Parent Updates Parent - java

When i add new car to persisted person and save car from car repository, person gets updated too. Why this happens or should be happen?
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String identityNumber;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "identityNumber", referencedColumnName = "identityNumber")
private List<Car> cars = new ArrayList<>();
#Enumerated(EnumType.STRING)
private Status status;
public enum Status{
SINGLE,
MARRIED
}
public void addCar(Car car){
cars.add(car);
}
}
#Entity
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String model;
private String identityNumber;
}
String identityNumber = UUID.randomUUID().toString();
Person person = Person.builder()
.firstName("affasf")
.lastName("asdasdsa")
.status(Person.Status.SINGLE)
.identityNumber(identityNumber)
.cars(new ArrayList<>())
.build();
personRepository.save(person);
Car car = Car.builder()
.identityNumber(identityNumber)
.model("BMW").build();
person.addCar(car);
person.setStatus(Person.Status.MARRIED);
carRepository.save(car);
return person;
I'm just trying to understand that is it expected behavior, if it's why? I couldn't find much on google. Thank you for your answers by advance.
Note : I removed the lombok annotations to keep the code shorter.

Related

Loading DTO with collection

#Entity
#Table(name = "person")
public class Consignment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "person_id")
private String personId;
#Column(name = "person_name")
private String personName;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "person")
#Column(name = "cars_owned")
private Set<Cars> casrsowned = new HashSet<>();
}
#Entity
#Table(name = "cars")
public class Cars implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "cars_id")
private String carsId;
#ManyToOne
#JoinColumn(name = "person")
private Person person;
#OneToOne
private CarsDetail carsDetail;
}
#Entity
#Table(name = "carsDetail")
public class CarsDetail implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "brand")
private String brand;
#Column(name = "color")
private String color;
#Column(name = "model")
private String model;
}
class CarModelDTO {
String personName;
List<String> models;
}
In the above relation, want to return CarModelDTO
JPA query where,
#Query("Select CarModelDTO(p.personName, p.casrsowned.carsDetail.model) from Person as p where p`enter code here`.id = :id"))
public CarModelDTO getCarmodelOwnedByAperson(#Param("id") Long id);
I tried multiple ways but it gives
org.hibernate.QueryException: illegal attempt to dereference collection
As I have already described Retrieve List from repository interface to DTO list you should go through the following step :
first create a constructor using the fields you want to be returned from the query output
in you query you should create new instance of your dto and pass the field from db to new instalnce :
so you need these changes:
1. In the constructor:
You should not use a list as List<String> models; as you should consider that your dto as a result row of DB. so you need to have a simple String model;
public CarModelDTO (String name,String model){
this.name=name;
this.model=model;
}
2. In the #Query:
you should use multi inner join appropriately
you should also append your package name to CarModelDTO in the query (here i used com.example you should change it)
#Query("Select com.example.CarModelDTO(p.personName, d.model ) from Person as p inner join p.carsowned c inner join c.carDetail d where p`enter code here`.id = :id"))
public CarModelDTO getCarmodelOwnedByAperson(#Param("id") Long id)

Hibernate "object references an unsaved transient instance"

My Users are in Organisations in a ManyToOne relationship, when a user is created with an existing Organisation I am trying to assign it to it without creating a new one.
In my service, here is how I create a user:
#Override
public UserInfo createUser(UserInfo newUser) {
// Check if organisation exists
OrganisationEntity orga = organisationDao.findByName(newUser.getOrganisation());
if (orga != null) {
// Organisation exists, we save it with the correct ID
return mapper.map(userDao.save(mapper.map(newUser, orga.getId())));
} else {
// Organisation does NOT exists, we save it and create a new one
return mapper.map(userDao.save(mapper.map(newUser, (long) -1)));
}
}
With my Mapper (helping me to convert a model to an entity) being:
public UserEntity map(UserInfo userInfo, Long orgaId) {
UserEntity user = new UserEntity();
user.setEmail(userInfo.getEmail());
user.setFirstName(userInfo.getFirstName());
user.setLastName(userInfo.getLastName());
user.setPassword(userInfo.getPassword());
OrganisationEntity orga = new OrganisationEntity();
orga.setName(userInfo.getOrganisation());
// We set the organisation's ID
if (orgaId != -1)
orga.setId(orgaId);
user.setOrganisation(orga);
return user;
}
And here is my UserDao:
#Transactional
public interface UserDao extends CrudRepository<UserEntity, Long> {
UserEntity save(UserEntity user);
}
And finally the relation in my UserEntity:
#ManyToOne(targetEntity = OrganisationEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name = "orga_id")
private OrganisationEntity organisation;
Creating a user with a new Organisation work but when I input an existing one, I get the following:
detached entity passed to persist
From my understanding it is a bidirectional consistency problem, but the answers did not help me so far.
Finally here are my Entity classes:
#Entity
#Table(name = "\"user\"")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String email;
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
private String password;
#ManyToOne(targetEntity = OrganisationEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name = "orga_id")
private OrganisationEntity organisation;
// Getters & Setters
}
and
#Entity
#Table(name = "organisation")
public class OrganisationEntity {
#Id
#Column(name = "orga_id", unique = true)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Column(unique = true)
private String name;
// Getters & Setters
}
I have solved my problem,
As you can see in the mapper above, I am creating a new instance of OrganisatonEntity no matter what, even if it already exists !
So a small change in my code solved it:
public UserEntity map(UserInfo userInfo, OrganisationEntity organisationEntity);
instead of
public UserEntity map(UserInfo userInfo, Long orgaId);
When the organisation already exists, I then assign it to my UserEntity like such:
user.setOrganisation(organisationEntity);
instead of instantiating a new object.
Problem solved !

JPA Criteria seems to ignore join on condition

I've a User and Contact entities in my app and I need to every user can add some private comment about every contact and that comment must be available only for that user. So I create new entity - PrivateInfo. Here's the code.
User class:
#Entity
#Table(name = "users")
#XmlAccessorType(XmlAccessType.FIELD)
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String login;
// other fields
}
Contact class:
#Entity
#Table(name = "contacts")
#XmlAccessorType(XmlAccessType.FIELD)
public class Contact implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#OneToMany(fetch = LAZY, cascade = ALL, mappedBy = "contact")
private Set<PrivateInfo> privateInfo;
// etc.
}
PrivateInfo class:
#Entity
#Table(name = "private_info")
#XmlAccessorType(XmlAccessType.FIELD)
public class PrivateInfo implements Serializable {
#EmbeddedId
private PrivateInfoKey pk;
#Column(name = "additional_info")
private String additionalInfo;
#ManyToOne(fetch = FetchType.EAGER)
#MapsId("contactId")
private Contact contact;
}
#Embeddable
public class PrivateInfoKey implements Serializable {
#Column(name = "contact_id")
private Long contactId;
#Column(name = "user_id")
private Long userId;
}
I'm using Spring Data repositories with JpaSpecificationExecutor for querying so here's my attempt to write specification for getting all contacts with private info for specific user.
public static Specification<Contact> withPrivateInfo(final long userId) {
return new Specification<Contact>() {
#Override
public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Join<Contact, PrivateInfo> joinPrivateInfo = root.join(Contact_.privateInfo, JoinType.LEFT);
joinPrivateInfo.on(cb.equal(
joinPrivateInfo.get(PrivateInfo_.pk).get(PrivateInfoKey_.userId), userId
));
return cb.conjunction(); // translates in sql like '... where 1 = 1'
}
};
}
However, when I call
contactRepository.findAll(withPrivateInfo(1));
I'm receiving contacts and each of them contains in privateInfo field all users information about this contact (not only for user with id = 1, as expected). Seems like join on condition ignored.
Any suggestions how to achieve my goal? Maybe with another entities structure. Is this possible with JPA/Criteria?

MVC SpringBoot and JPA Relationship

I have an issue with the JPA relationship within a MVC SpringBoot application.
I'm trying to create an application where I have a number of users and every user can have a number of cars. A user can have multiple cars. I've done a #OneToOne relationship between the user and the car object, here's what I've done:
here is the User class:
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "username", nullable = false)
private String username;
#Column(name = "password", length = 500, nullable = false)
private String password;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Car> cars;
}
then here is the Car class:
#Entity
#Table(name = "car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(length = 11)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "make", nullable = false)
private String make;
#Column(name = "model", nullable = false)
private String model;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id")
private User user;
}
Then, here is the actual service implementation
#Component
#Transactional(readOnly = true)
public class CarServiceImpl implements CarService {
#Inject
private CarRepository carRepository;
#Inject
private UserRepository userRepository;
#Override
#Transactional(readOnly = false)
public Car addCar(Long userId, Car car) {
User user = userRepository.findOne(userId);
user.getGpsLocationModels().add(car);
car.setUser(user);
carRepository.save(car);
return car;
}
then I have the endpoint but that works fully. The add method looks like does work as supposed to, at least I get the expected output, however the find method I have no idea how to write it, well can't figure it out how to retrieve cars based on user, I know how to get them by their ID but not for every user separately.
Here is my try:
#Override
public Car findCar(Long userId, Long carId) {
//get the current user (that comes as JSON Request Param)
User user = userRepository.findOne(userId);
//get the car based on its ID, here's the problem, I want the car based on its ID AND its user, I can't display cars which aren't for that particular user
Car car = carRepository.findOne(carId);
return car;
}
Here is the method for get all cars for a particular user:
#Override
public List<Car> displayAllCars(Long userId) {
return userRepository.findOne(userId).getCars();
}
I'd really appreciate any help that you could advise.
Thank you so much
Your mappings are incorrect. Car > User is #ManyToOne. If you also make this bi-directional you can also then retrieve the cars via the user:
#Entity
#Table(name = "user")
public class User implements Serializable {
#OneToMany(mappedBy ="user",cascade = CascadeType.ALL)
private Set<Car> cars;
public Set<Car> getCars(){
return cars;
}
public void addCar(Car car){
cars.add(car);
car.setUser(this);
}
}
#Entity
#Table(name = "car")
public class Car implements Serializable {
#ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="user_id")
private User user;
}
#Override
public Set<Car> findCars(Long userId) {
return userRepository.findOne(userId).getCars();
}
You could have a method that accepts the user ID and returns the list in the car repository like:
List<Car> findCarByUser(Long userID);
And then you will have
#Override
public List<Car> displayAllCars(Long userId) {
return carRepository.findCarByUser(userId);
}

Persisting a #ManyToOne-referenced object only if it does not exist

I'm fairly new to Spring/JPA so this is somewhat a trivial question.
I have two entities with a many-to-one relationship: Item and ItemType. Basically, ItemType simply represents a unique name for a set of Items. I use a CrudRepository<Item, Long> to store them. The relevant code is as follows (getters/setters/equals()/hashCode() omitted):
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "type_id")
private ItemType itemType;
public Item() {}
public Item(ItemType itemType) {
this.itemType = itemType;
}
}
#Entity
public class ItemType {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true, nullable = false)
private String name;
public ItemType() {}
public ItemType(String name) {
this.name = name;
}
}
#Controller
public class ItemsController {
#Autowired private ItemsRepo itemsRepo;
#RequestMapping(value = "/item", method = RequestMethod.POST)
#ResponseBody
public Item addQuestionSet(#RequestBody Item item) {
return itemsRepo.save(item);
}
}
When I insert a new Item into the database, I want it to get a type_id from either an ItemType with the given name if it already exists, or from a newly persisted ItemType otherwise.
As of now, I naturally get an exception when trying to insert the second item with the same type:
org.hsqldb.HsqlException: integrity constraint violation: unique constraint or index violation
I could probably make a boilerplate check in my controller before saving a new item into repository. But this task is rather generic, I'm pretty sure there must be a convenient solution in JPA.
Thanks.
It seems you are persist() method on the Item object rather than merge() method. I hope it will resolve your query.
I can see that the problem is when you "persist", try with "lazy" type. You could get the data only when you need it and EAGER always.
I can give you an example how i do it
this is my class "CentroEstudio"
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idCentroEstudio",nullable=false)
private Long idCentroEstudio;
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name = "idTipoCentroEstudio", nullable = false)
private TipoCentroEstudio tipoCentroEstudio;
#Column(name="nombre",nullable=false)
private String nombre;
#Column(name="activo",nullable=false)
private boolean activo;
this is my class "TipoCentroEstudio"
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="idTipoCentroEstudio",nullable=false)
private Long idTipoCentroEstudio;
#Column(name="descripcion",nullable=false)
private String descripcion;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "tipoCentroEstudio")
private Set<CentroEstudio> centroEstudio = new HashSet<CentroEstudio>(0);
I'm sorry for the Spanish in the example, but I'm peruvian and I speak Spanish.
I hope this helps you ...

Categories