I have a Product :
#Data
#Entity
#Table(name = "products", schema = "laboratory", catalog = "laboratory")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Product {
#Id
#GeneratedValue
private int id;
#ManyToOne(fetch = FetchType.LAZY, cascade= CascadeType.ALL)
#JoinColumn(name = "project_id")
#Transient
private Project project; // this one is for read only
#Column(name="project_id") // this will save the id in db for the project
private int projectId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Inspection inspection;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Information information;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Departure departure;
private String un;
private String tc;
}
There is 3 class that this product needs in order to be a Product : Information, Inpection, Departure
All 3 of these classes are similar.
I want to link them by the Product.id witch is a #GeneratedValue AI in sql.
Here is one of the 3 class :
Information
#Data
#Entity
#Table(name = "products_informations", schema = "laboratory", catalog = "laboratory")
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Information {
#Id
private int productId;
private String description;
private String model;
private int year;
private String serialNumber;
private int odometre;
private int noCrochet;
private int nbKeys;
private String localisation;
private String cemeteryPosition;
#JsonFormat(pattern = "yyyy-MM-dd")
private Date receptionDate;
}
I want, WHEN I save() the product, that the private String productId in this class to automatically take the Id from the Product class without having to do it manually in my controller.
You have the mappings backwards in your model.
By using
public class Product {
#Id
#GeneratedValue
private int id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="id")
private Information information;
You've told JPA to use the PRODUCT.ID primary key as a foreign key to the Information table; foreign keys are controlled by the relationship, so it means your ID value is pulled from the information.productId value. Opposite of what you are asking for and it means you have 4 mappings trying to set the PRODUCT.ID column value (set them different and see for yourself).
Try this instead:
public class Product {
#Id
#GeneratedValue
private int id;
#OneToOne(mappedby="product", cascade = CascadeType.ALL)
private Information information;
..
}
public class Information {
#Id
private int productId;
#MapsId
private Product product;
..
}
With this you will need to set the Information.product reference, but JPA will use that to set your productId value, using the one you set within the product.id property. You just need to set this relationship when you add an Information instance to a product. Do the same for the other relationships
Related
I'm new at Spring Boot's JPA concept so need your help in deciding how to import just the ID of another entity, say User into HealthData entity. Following is my User entity:
#Entity
#Table(name = "user",uniqueConstraints = {#UniqueConstraint(columnNames = "email")})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#Email
#Column(nullable = false)
private String email;
private String imageUrl;
#Column(nullable = false)
private Boolean emailVerified=false;
#JsonIgnore
private String password;
#NonNull
#Enumerated(EnumType.STRING)
private AuthProvider authProvider;
private String providerId;
}
And I wish to define HealthData entity in the following manner :
#Entity
#Table(name = "HealthData",uniqueConstraints = {#UniqueConstraint(columnNames = "id")})
public class HealthData {
#Id
private Long id; //how to import id of User here?
#Column
private Double height;
#Column
private Double weight;
#Column
private int age;
...other columns
}
Now, I wish to use Id of User to this entity(kind of making parent-child relationship) . I don't want to add User class object in HealthData. I thought of using #OneToOne in HealthData but then it would add User in it. How can i just include Id from parent table in child table?
In this case, your HealthData has a reference to User, and I'm not sure why you wouldn't have mapped this as a foreign key. If you are able to do so, I'd suggest the following:
#Entity
#Table(name = "HealthData")
public class HealthData {
#Id
#OneToOne
#JoinColumn(name = "id")
private User user;
#Column
private Double height;
#Column
private Double weight;
#Column
private int age;
...other columns
}
JPA then handled setting the "ID" to the value within your user instance for you, and can persist both in the same transaction automatically. Allowing references to be marked as IDs is known as a derived ID and supported I believe since JPA 2.0.
As for efficiency, you can still lazy fetch or even not fetch the user instance. It is simple to just map the ID column as a basic using a slightly different approach:
#Entity
#Table(name = "HealthData")
public class HealthData {
#Id
private Long id;
#MapsId
#OneToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "id")
private User user;
#Column
private Double height;
#Column
private Double weight;
#Column
private int age;
...other columns
}
JPA will set both the User id as well as the healthData.id values based on what it generates for the user Id sequence when you set the healthData.user reference.
You can use getters and setters to set the value of user id in the healthdata table.
I want to get data from multiple table.
public class Student{
private int id;
private String name;
private List<Course> course;
}
public class Course{
private int id;
private String name;
private int studentId;
}
I want to fetch data from student and course table using spring data jpa and map to student object.
How can I do that in efficient way?
#Data
#NoArgsConstructor
public class Student{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#OneToMany(mappedBy="studentId",cascade=CascadeType.ALL,fetch=FetchType.Eager)
private Set<Course> course;
}
You May Use Set Instead of List.
Always Use Mapped By in OneToMany Side, If you use it manyToOne side it will create an
extra table.
You can use Fetch Type eager or lazy. By default, it is lazy with You have
to use #transactional of Lazy.
#Data
#NoArgsConstructor
public class Course{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#ManyToOne
#JoinColumn(name="studentId")
private int studentId;
}
Hope this Answer Solve your query Happy Coding!.
Note that the starting point might be wrong. I assume that a student can choose multiple courses and a course can be chosen by multiple students. So it is actually a #ManyToMany relationship but not #ManyToOne or #OneToMany.
You will definitely need a joint table to map their primary keys from two tables into the joint table.
#Entity
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#EqualsAndHashCode.Exclude
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinTable(
name = "courses",
joinColumns = #JoinColumn(name = "student_id"),
inverseJoinColumns = #JoinColumn(name = "course_id"))
private Set<Course> courses;
}
#Entity
#Data
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#JsonIgnore
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "courses",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private Set<Student> students;
}
Note all the modifications I have here.
For the data persisted into database, Long is a better choice than int. Similarly, e.g., use Boolean instead of boolean.
Think the Student as the side managing the many-to-many relationship, and Course as the target side. On the target side, use #JsonIgnore and #ToString.Exclude annotations to avoid an infinite recursion, StackOverflow or OOM.
#JsonIgnore
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToMany(mappedBy = "courses",fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Use Set instead of List if a student is not supposed to select the exact same course. It ensures that one can still select 2017 fall Maths and 2018 fall Maths, while one cannot select 2017 fall Maths twice.
I'm working on a Spring-Boot project with a H2 database. I have two entities Portfolio and Report, and there is a many-to-many association between the two.
I want those entities to be audited, so I followed this tutorial to audit through an AuditorAware interface with custom fields.
The two entities are well audited, the columns are created in the database. However, the join table portfolio_reports is not audited. How can I audit the join table as well ?
Portfolio.java
#Entity
#Table(name = "portfolio")
public class Portfolio extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
#Unique
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "portfolio_report", joinColumns = #JoinColumn(name = "portfolio_id"), inverseJoinColumns = #JoinColumn(name = "report_id"))
private List<Report> reports;
// Getters and setters
}
Report.java
#Entity
#Table(name = "report")
public class Report extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "axioma_id")
private Long axiomaId;
#Column(name = "name")
private String name;
#AuditJoinTable
#ManyToMany(mappedBy = "reports", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
private List<Portfolio> portfolios;
// Getters and setters
}
Auditable.java
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#Version
#Column(name = "version_no")
protected Long versionNo;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date")
protected Date createdDate;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modified_date")
protected Date modifiedDate;
}
AuditorAwareImpl.java
public class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Admin");
}
}
PersistenceConfiguration.java
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class PersistenceConfiguration {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditorAwareImpl();
}
}
Problem:
Clearly here Auditable should add some column to your intermediate table that maintains relation between Portfolio and Report and that table is created behind the scene and you don't have access to that table in your program. Only hibernate can use that table to maintain relation between your entities and do join operation.
Solution:
Here you should make Join table that maintain Many to Many relation between Portfolio and Report explicit so that you can have entity like PortfolioReport in your program that can extends from Auditable. Please read the following post to see how to do that: The best way to map a many-to-many association with extra columns when using JPA and Hibernate
I work with an embedded H2 database in which I use the #OneToMany relationship to relate an entity instance (product) to multiple instances of the other entities (suppliers); it's useful when I have specific suppliers for a particular product.
However now, I want to associate all the suppliers with every single product; I don't want to generate in my supplier table different supplier records for each product, instead I want to have only 5 records (5 suppliers) in my supplier table which are associated to every single product, it few words I want to achieve something like "one to all", is it possible to do it using JPA annotations?
Product entity
#Entity
public class Product {
#Id
private String productCode;
#OneToMany
#JoinColumn(name = "supplier_id", referencedColumnName = "productCode")
private List<Supplier> suppliers;
}
Supplier entity
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String name;
}
Unidirectional #OneToMany association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // according to your need
private List<Supplier> suppliers;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
...
}
#ManyToOne association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "product_id", foreignKey = #ForeignKey(name = "PRODUCT_ID_FK"))
private Product product;
private String name;
...
}
I have a Question Entity and Tag entity with getter, setter methods and a OneToMany relationship from question to tag and a OneToOne relationship from question to user
public class Question {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="title")
private String title;
#Column(name="body")
private String body;
#Temporal(TemporalType.DATE)
#Column(name="date_created")
private Date date_created;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="user_id")
private User user;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="tag_id")
private Tag tag;
#Column(name="answer_count")
private int answer_count;
#Column(name="view_count")
private int view_count;
public Question() {
}
Tag entity
public class Tag {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="name")
private String name;
#Column(name="username")
private String username;
#Temporal(TemporalType.DATE)
#Column(name="date_created")
private Date date_created;
public Tag() {
}
When I try to insert a question using Postman with the following details:
{
"title": "stefanyyyxx",
"body": "stefandoyee44",
"date_created": "2019-02-27",
"user_id" : 1,
"tag_id": 1,
"answer_count": 0,
"view_count": 0
}
QuestionRepository.java:
#Override
public void save(Question theQuestion) {
// get the current hibernate session
Session currentSession = entityManager.unwrap(Session.class);
// save employee
currentSession.saveOrUpdate(theQuestion);
}
Null values are being inserted for user_id and tag_id though I used JoinColumn().
MySQL:
As #Karol Dowbecki Suggested,
convert the JSON to DTO object and use that DTO to get the User, Tag Entities from their respective repositories.
Finally create the Question entity object and store it.
Question Entity
#Entity
#Table(name = "question")
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "title")
private String title;
#Column(name = "body")
private String body;
#Temporal(TemporalType.DATE)
#Column(name = "date_created")
private Date dateCreated;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "tag_id")
private Set<Tag> tag;
#Column(name = "answer_count")
private int answerCount;
#Column(name = "view_count")
private int viewCount;
}
User Entity
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Tag Entity
#Entity
#Table(name = "tag")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "username")
private String username;
#Temporal(TemporalType.DATE)
#Column(name = "date_created")
private Date dateCreated;
}
DTO Class
public class QuestionDTO {
private Long id;
private String title;
private String body;
private Date dateCreated;
private Long user;
private Long tag;
private int answerCount;
private int viewCount;
}
Test Class
#Service
public class TestService {
#Autowired
private QuestionRepository questionRepository;
#Autowired
private UserRepository userRepository;
#Autowired
private TagRepository tagRepository;
public void addQuestion(QuestionDTO dto) {
Tag tag = null;
User user = null;
Question question = null;
Set<Tag> tags = null;
tag = tagRepository.findById(dto.getTag());
tags = new HashSet<>();
tags.add(tag);
user = userRepository.findById(dto.getUser());
question = new Question();
question.setTag(tags);
question.setUser(user);
question.setId(dto.getId());
question.setBody(dto.getBody());
question.setTitle(dto.getTitle());
question.setViewCount(dto.getViewCount());
question.setAnswerCount(dto.getAnswerCount());
question.setDateCreated(dto.getDateCreated());
questionRepository.save(question);
}
}
NOTE :
The relation between Question and Tag are in OneToMany you have to use Collection type.
You have a mismatch between JSON and #Entity structure. JSON contains numeric identifiers while the #Entity contains actual objects representing relationships. You most likely should introduce a separate DTO class to map this JSON while in #Repository you should load User and Tag objects based on their id or create new ones. You already have CascadeType.ALL so Hibernate will cascade the persist operation.
Generally the controller layer should be separate from repository layer unless you are doing something very, very simple. This helps to evolve the service without changing the API contract e.g. adding new columns for auditing changes. By exposing the #Entity as DTO you make your life harder down the road.
You should add referencedColumnName in your Child Entity Foreign Key Column
referencedColumnName="your primaray key column name"
EDIT:
referencedColumnName
The name of the column referenced by this foreign key column.
When used with entity relationship mappings other than the cases
described here, the referenced column is in the table of the target
entity.
When used with a unidirectional OneToMany foreign key mapping, the
referenced column is in the table of the source entity.
When used inside a JoinTable annotation, the referenced key column is
in the entity table of the owning entity, or inverse entity if the
join is part of the inverse join definition.
When used in a CollectionTable mapping, the referenced column is in
the table of the entity containing the collection.
Default (only applies if single join column is being used): The same
name as the primary key column of the referenced table.
Asset is Parent Entity and AssetDetails is Child Entity
Here I have taken OneToOne Relationship
Asset.java
#Entity
#Table(name="asset")
public class Asset {
#Id
#GeneratedValue
#Column(name="assetid")
private BigInteger assetid;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "asset")
#JsonBackReference
private AssetDetails assetDetails;
public AssetDetails getAssetDetails() {
return assetDetails;
}
public void setAssetDetails(AssetDetails assetDetails) {
this.assetDetails = assetDetails;
assetDetails.setAsset(this);
}
public Asset(your fields, AssetDetails assetDetails) {
super();
// your fields
this.assetDetails = assetDetails;
this.assetDetails.setAsset(this);
}
public Asset() {
super();
}
public BigInteger getAssetid() {
return assetid;
}
public void setAssetid(BigInteger assetid) {
this.assetid = assetid;
}
}
AssetDetails.java
#Entity
#Table(name="assetDetails")
public class AssetDetails {
#Id
#GeneratedValue
private BigInteger assetdetailid;
#JoinColumn(name = "assetid",nullable = false, updatable = false,referencedColumnName="assetid")
#OneToOne(cascade=CascadeType.ALL)
#JsonManagedReference
private Asset asset;
public Asset getAsset() {
return asset;
}
public void setAsset(Asset asset) {
this.asset = asset;
}
public AssetDetails(your fields,Asset asset) {
super();
//your fields
this.asset = asset;
}
}