I have created a few entities. A class main with a field Set<roles>.
roles is the super class. Actually I want to store there only subclasses.
This all works when I run it in the java code. Now I use Spring Boot and want to get the data in via API. Unfortunately Hibernate does not understand the JSON and only saves the fields of roles and does not recognize the actual subclasses.
I have added a DiscriminatorColumn. But this is not exported to JSON at all and if I specify this in the JSON, it is ignored.
What can I do to make Hibernate recognize the correct class?
Examples:
#Entity
#Table(name = "main")
#Inheritance(strategy = InheritanceType.JOINED)
public class main {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uniqueId;
private String title;
#OneToMany(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private Set<Roles> roles = new HashSet<>();
}
#Entity
#Table(name = "roles")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "objectType",
discriminatorType = DiscriminatorType.STRING)
public class Roles {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uniqueId;
private String city;
}
#Entity
#Table(name = "person")
#DiscriminatorValue("Person")
public class Person extends Roles {
private String firstname;
private String lastname;
}
#Entity
#Table(name = "company")
#DiscriminatorValue("company")
public class Company extends Roles {
private String companyname;
}
If I now send this JSON to my API:
{
"title": "a Test",
"roles": [
{
"city": "Testcity",
"comopanyname": "test inc."
},
{
"city": "Disney",
"fistname": "Mickey",
"lastname": "Maus"
}
]
}
Hibernate will only save the roles field city. No data are in company or person.
The DiscriminatorColumn has the same value roles in both cases.
Also if I add the DiscriminatorColumn objectType in json
{
"title": "a Test",
"roles": [
{
"objectType": "Company",
"city": "Testcity",
"comopanyname": "test inc."
},
{
"objectType": "Person",
"city": "Disney",
"fistname": "Mickey",
"lastname": "Maus"
}
]
}
does nothing.
What can I do?
Related
I have Book entity and Author entity having many-to-many relationship. I have a book like this: (author A has id=1 in database and I use #GeneratedValue(strategy = GenerationType.IDENTITY) so I don't want to add id)
{
"id": 1,
"name": "Spring Boot 1"
"authors": [
{
"name": "A"
}
]
}
Then I create another book like this
{
"id": 2,
"name": "Spring Boot 2"
"authors": [
{
"name": "A"
},
{
"name": "B"
}
]
}
The problem is Spring JPA will create another author "A" with id=2 in the database and author "B" with id=3.
What I want is JPA use existing author "A" and create an author "B" with id = 2.
Book
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private double price;
#ManyToMany(
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
#JoinTable(
name = "book_author",
joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "author_id")
)
#JsonIgnoreProperties("books")
private List<Author> authors = new ArrayList<>();
}
BookService
#Service
public class BookService {
#Autowired
BookRepository bookRepository;
public List<Book> getAllBook() {
return bookRepository.findAll();
}
public Book saveBook (Book book) {
return bookRepository.save(book);
}
public void deleteBook(Long id) {
bookRepository.deleteBookById(id);
}
}
BookController
#RestController
#RequestMapping("/book")
public class BookController {
#Autowired
BookService bookService;
#GetMapping
public ResponseEntity<List<Book>> getAllBook() {
return new ResponseEntity<List<Book>>(bookService.getAllBook(), HttpStatus.OK);
}
#PostMapping
public ResponseEntity<Book> saveBook(#RequestBody Book book) {
return new ResponseEntity<Book>(bookService.saveBook(book), HttpStatus.CREATED);
}
}
Author
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "author")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany(mappedBy = "authors", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnoreProperties("authors")
private List<Book> books = new ArrayList<>();
}
AuthorService and AuthorController are similar to Book
Is there any annotations or query available to fix this problem ? ? Or what is the most efficient way to do it ?
I don't want to use this solution below because I think there is a solution available in JPA, shorter and more efficient than this and I just don't know
I illustrate this method using ChatGPT
public ResponseEntity<Book> createBook(#RequestBody Book book) {
for (Author author : book.getAuthors()) {
Optional<Author> existingAuthor = authorRepository.findOne(
Example.of(author, ExampleMatcher.matching().withIgnorePaths("id"))
);
if (existingAuthor.isPresent()) {
book.addAuthor(existingAuthor.get());
}
}
Book savedBook = bookService.save(book);
return ResponseEntity.created(URI.create("/books/" + savedBook.getId())).body(savedBook);
}
How can JPA know if there aren't 2 authors with the same name? It can't.
If you want to 'reuse' an existing author then you have to locate and set that yourself.
A little tip, do NOT use the classes in your database model for your DTOs.
I want to create a spring boot rest controller with this specification :
Customers of an electricity and gas supply company can choose to receive their monthly bills either by email or by regular mail, neither or both.
My goal is to create java hibernate entities to manage these customers and their choices of sending bills.
A utility customer is identified by their email and can have multiple choice change events that change the customer choice status.
Each choice made by a customer generates a choice change event.
These my classes:
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Email(message="this field must respect the email format !")
private String email;
#OneToMany(mappedBy = "customer")
#MapKey(name="type") //don't need this if you want just a list
private Map<String, Choice> choices;
}
#Entity
#Table(name = "choices")
public class Choice {
#Id
#OneToOne
private Customer customer;
#Id
#Enumerated(EnumType.STRING)
private ChoiceType type;
#Column(name="enabled")
private boolean enabled;
}
#IdClass(EmployeeId.class)
public class ChoiceId implements Serializable {
private Integer customer;
private ChoiceType type;
}
public enum ChoiceType {
MAIL,
EMAIL;
}
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String email;
#OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private List<Choice> choices;
//Getters and Setters
}
#Entity
#Table(name = "choices")
#IdClass(ChoiceId.class)
public class Choice {
#Id
#OneToOne
private Customer customer;
#Id
#Enumerated(EnumType.STRING)
private ChoiceType type;
#Column(name="enabled")
private boolean enabled;
//Getters and Setter
}
public class ChoiceId implements Serializable {
private Integer customer;
private ChoiceType type;
//Getters and Setters
}
public enum ChoiceType {
MAIL,
EMAIL;
}
Services:
CustomerServiceImpl
public CustomerDto addCustomer(CustomerDto customer) {
if(customer.getChoices() !=null) {
for(int i=0; i < customer.getChoices().size(); i++) {
ChoiceDto choice = customer.getChoices().get(i);
choice.setCustomer(customer);
customer.getChoices().set(i,choice);
}
} else {
customer.setChoices(Collections.<ChoiceDto>emptyList());
}
ModelMapper modelMapper = new ModelMapper();
Customer customerEntity = modelMapper.map(customer, Customer.class);
Customer newCustomer = customerRepository.save(customerEntity);
CustomerDto customerDto = modelMapper.map(newCustomer, CustomerDto.class);
return customerDto;
}
ChoiceServiceImpl
public ChoiceDto addChoice(ChoiceDto choiceDto, String email) {
Customer customerWithChoice = customerRepository.findByEmail(email);
ModelMapper modelMapper = new ModelMapper();
CustomerDto customerDto = modelMapper.map(customerWithChoice, CustomerDto.class);
choiceDto.setCustomer(customerDto);
Choice choiceEntity = modelMapper.map(choiceDto, Choice.class);
Choice newChoice = choiceRepository.save(choiceEntity);
ChoiceDto newChoiceDto = modelMapper.map(newChoice, ChoiceDto.class);
return newChoiceDto;
}
I want to get these results :
Post(create customer):
{
"id": "1289",
"email": "customer#eamil.com",
"choices": []
}
If two events are created in this order:
Post(create choices):create of two choices in this order
{
"user": {
"id": "1289"
},
"choices": [
{
"id": "MAIL",
"enabled": true
}
]
}
{
"user": {
"id": "1289"
},
"choices": [
{
"id": "MAIL",
"enabled": false
},
{
"id": "EMAIL",
"enabled": true
}
]
}
GET particular customer :
{
"id": "1289",
"email": "customer#eamil.com",
"choices": [
{
"id": "MAIL",
"enabled": false
},
{
"id": "EMAIL",
"enabled": true
}
]
}
But it doesn't give me the same result ? How can i resolve this problem in my entities or service implementation
I'm having some troubles attempting to do a post request to a bridging entity via postman
My service class for clientBook:
public ClientBook create(ClientBook clientBook) {
return this.clientBookIRepository.save(clientBook);
}
The clientBookIrepository is just a class that extends JpaRepository
My client class
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#SuperBuilder
public class Client implements Serializable {
#Id
#Column(name="clientId")
private String clientId;
#Embedded
#Column(name="name")
private Name name;
#OneToMany(mappedBy = "client")
private Set<ClientBook> clientBookSet;
#OneToMany(mappedBy = "client")
private Set<ClientContact> clientContactSet;
}
My book class
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#SuperBuilder
public class Book implements Serializable {
#Id
#Column(name = "bookId")
private String bookId;
#NotNull
private String bookName;
#NotNull
private String author;
#NotNull
private String genre;
#NotNull
private String description;
#NotNull
private String isRented;
#NotNull
private String imgUrl;
#OneToMany(mappedBy = "book")
private Set<ClientBook> clientBookSet;
}
My clientBook class
#Entity
#IdClass(ClientBookId.class)
public class ClientBook implements Serializable {
#Id
#ManyToOne
#PrimaryKeyJoinColumn(name="bookId",referencedColumnName = "bookId")
private Book book;
#Id
#ManyToOne
#PrimaryKeyJoinColumn(name="clientId",referencedColumnName = "clientId")
private Client client;
//Getters, setters, constructor and builder pattern here
I tested it 2 ways
Option 1:
{
"client": {
"clientId": "3",
"name": {
"firstName": "ABC",
"middleName": "",
"lastName": "123"
}
},
"book": {
"bookId": "1",
"bookName": "Hunger Games",
"author": "Suzanne Collins",
"genre": "Fiction",
"description": "The Hunger Games film series is composed of science fiction dystopian adventure films",
"isRented": "Available",
"imgUrl": "https://images-na.ssl-images-amazon.com/images/S/compressed.photo.goodreads.com/books/1586722975i/2767052.jpg",
"clientBookSet": null
}
}
which returns status code 500 and returns this in the console:
Resolved [org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.String' to required type 'testing.domain.Client' for property 'client'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'testing.domain.Client' for property 'client': no matching editors or conversion strategy found]
and option 2
{
"clientId": 3,
"bookId": 1
}
which also returns status code 500 and returns this in the console:
Column 'book_book_id' cannot be null
I have two tables which are related to each other, table "user" and "address":
#Entity
#Table(name = "user")
#Data
public class User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotNull
#Column(unique = true)
private String user_name;
#Column
private String description;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
protected Set<Address> addresses= new HashSet<>();
}
While in the other table:
#Entity
#Table(name = "address")
#Data
public class Address{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
protected User user;
private String description;
}
I did a post request to create new user with some addresses:
#PostMapping ("/user/create")
public ResponseEntity post(#RequestBody User user) {
userService.saveNewUser(user);
// return
}
In a post request I sent this json:
{
"user_name": "example",
"description": "this is a user description",
"addresses": [
{
"description": "this is a address 1"
},
{
"description": "this is a address 2"
}
]
}
When I insert data I get the "address" table the "user_id" is null, the data are inserted but the relations are not there?
What I'm doing wrong here? Please help!
Update: Let's say I have this method saveNewUser on the service, how to call update Address?
public Oject saveNewUser(User user ) {
//
return jpaRepository.save(user);
}
You have to support all relations manually. Hibernate doesn't do it for you.
So you need to set a user for each address.
public Oject saveNewUser(User user ) {
user.getAddresses().forEach(address -> address.setUser(user));
return jpaRepository.save(user);
}
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.