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;
}
}
Related
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
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 have three tables that have one to one relation the first table is the province entity, the district entity which has a one to relation with the province and the person's entity as each person resides in on district.
I would like to access the district information in thymleaf.
#Entity
#Table (name = "provinces")
public class Province {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int provinceId;
#Column(name="province_name",unique=true,length = 25, nullable = false)
private String provinceName;
//getters and setters
}
#Entity
#Table (name = "districts")
public class District {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique=true,length = 25, nullable = false)
private String districtName;
#Column(unique=true, length = 10,nullable = false)
private String abbreviation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="province")
private Province location;
}
#Entity
public class AppUser implements UserDetails {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private long id;
private String firstName;
private String lastName;
private String email;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "location_id")
private District location;
//getters and setters
}
service controller:
#GetMapping("/all")
private String viewDistricts (Model model){
model.addAttribute("listUsers",appUserService.listUser());
return "admin/users";
}
service:
public List<AppUser> listUser(){
return appUserRepository.findAll();
}
thymeleaf:
th:text="${users.location.districtName}"
org.springframework.expression.spel.SpelEvaluationException: EL1007E:
Property or field 'districtName' cannot be found on null
best practice to handle null pointer exception as follows
th:text="${users.location!=null}?${users.location.districtName}:''"
But, the root cause of the problem is that the associated object value is null.
I am using Spring Boot framework with Hibernate and JPA, and Thymeleaf for the view element. I want to show related data from 2 different entities on the same HTML table on a page. However the object which should contain the data from my joined entity is returning as null.
EDIT: The root cause of the problem is that the query which is being executed is joining my two tables on the 2 primary keys rather than on the primary key of my MsgOrig table and the foreign key of my ReplyMessage table ("msgOrigID") as intended. Any idea how I can correct this?
Also, just to get this working I am trying to return all messages. However my ultimate aim is to only return messages sent by the logged-in user. Is there a JPA repository method which could be used for this purpose? Or should I write a custom query instead?
Entity Class for MsgOrig:
#Entity
#Table(name = "messageoriginator")
public class MsgOrig {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "Date")
private String date;
#Column(name = "MsgTime")
private String msgTime;
#Column(name = "message")
private String msg;
#Column(name = "email")
private String email;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "msgOrigID")
private ReplyMessage reply;
//getters and setters
}
Entity Class for ReplyMessage:
#Entity
#Table(name = "replymessage")
public class ReplyMessage {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "msgOrigID")
private int msgOrigId;
#Column(name = "Date")
private String date;
#Column(name = "MsgTime")
private String msgTime;
#Column(name = "message")
private String msg;
#Column(name = "email")
private String email;
#OneToOne(mappedBy="reply")
private MsgOrig msgOrig;
//getters and setters
}
Controller Method:
#RequestMapping(value={"/mymessages"}, method = RequestMethod.GET)
public ModelAndView messages(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("mymessages", messageService.listAllMessages());
return modelAndView;
}
Implementation Class For MessageService interface:
#Service("messageService")
public class MessageServiceImpl implements MessageService{
#Autowired
private MessageRepository messageRepository;
#Override
public List<MsgOrig> listAllMessages() {
return messageRepository.findAll();
}
}
Repository class:
#Repository("messageRepository")
public interface MessageRepository extends JpaRepository<MsgOrig, Integer> {
#Query("SELECT t.email FROM MsgOrig t where t.id = :id")
String findUsersEmail(#Param("id") int id);
}
Relevant section on Thymeleaf template (returns "NOT FOUND"):
<td th:text="${mymessages.reply != null ? mymessages.reply.msgTime : 'NOT FOUND'}"/>
I have the below JSON as input:
{
"type": "Student",
"numOfPeople": "1",
"tenantMembers": [
{
"firstName": "Chris",
"lastName": "C"
}
],
"tenantDetails": {
"firstName": "John",
"lastName": "J",
"email" "xyz#gmail.com"
}
}
I want to use this to do a save:
tenantRepo.save(tenant);
This should save the parent "Tenant" and the children "TenantMembers" and "TenantDetails".
But when I do it does with NULL 'tenant_id's in the children. (If I have foreign keys in the DB gives 'tenant_id' can't be null constraint exception)
My question is: Is this possible in Hibernate?
My models:
Parent class:
#Entity
#Table(name = "tenant")
public class Tenant {
#GeneratedValue
#Id
private Long id;
private String type;
#Column(name = "num_of_people")
private String numOfPeople;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TenantMember> tenantMembers;
#OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL)
private TenantDetails tenantDetails;
TenantMember child class:
#Entity
#Table(name = "tenant_member")
public class TenantMember {
#GeneratedValue
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
TenanatDetails child class:
#Entity
#Table(name="tenant_details")
public class TenantDetails {
#GeneratedValue
#Id
private Long id;
#OneToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
EDIT:
Following up Dragan Bozanovic's suggestion, tried using #JsonIdentityInfo
for the three tables:
#Entity
#Table(name = "tenant")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Tenant {
#Entity
#Table(name="tenant_details")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantDetails {
#Entity
#Table(name = "tenant_member")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantMember {
and did the following to save:
#RequestMapping(value = "/set", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Tenant test(#RequestBody Tenant tenant) {
Tenant t = new Tenant();
t.setType(tenant.getType());
t.setNumOfPeople(tenant.getNumOfPeople());
tenantRepo.save(t);
tenant.setId(t.getId());
tenant.getTenantDetails().setTenant(tenant);
for(TenantMember member: tenant.getTenantMembers()) {
member.setTenant(tenant);
}
return tenantRepo.save(tenant);
}
Would this be the best approach that is possible?
Hibernate does save the children (hence the constraint violation) because of the cascading options you specified, but it does not save the relationship information (join column value) in your case.
TenantMember and TenantDetails are the owners of the association with Tenant (mappedBy attributes in the association annotations in Tenant).
That means that you have to properly update the tenant field in the TenantMember and TenantDetails instances, because Hibernate ignores inverse side of the association when maintaining the relationship.