Spring boot variable re-naming issue (Hibernate) - java

My variable names in entity class are assignTo and assignBy, The column name of the above in MySQL db are assignto and assignby respectively.
But project is creating it by name "assignmentTo" and "assignmentBy".
I have drop the whole databases and re-created with mvn clean install after deleting .m2/repository folder.
Postman is still returning "assignmentTo" and "assignmentBy" on GET API.
Called POST method with "assignTo" and "assignBy" names, still got "assignmentTo" and "assignmentBy".
Class LeadAssignment:
#Entity
#Table(name = "lead_assignment")
public class LeadAssignment {
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY )
#Column(name = "laid", nullable = false, updatable = false)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstname;
#Column(name = "last_name", nullable = false)
private String lastname;
#Column(name = "assignto" , nullable = false)
private String assignTo;
#Column(name = "assignby", nullable = false)
private String assignBy;
#Column(name = "requirement" , nullable = false)
private String requirements;
#Column(name = "remark" , nullable = false)
private String remarks;
Controller class LeadAssignmentController :
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(value = "/leadassignment")
public class LeadAssignmentController {
//#Autowired
private LeadAssignmentDao leadAssignmentDao;
LeadAssignmentController(LeadAssignmentDao leadAssignmentDao){
this.leadAssignmentDao = leadAssignmentDao;
}
#GetMapping("/getall")
List<LeadAssignment> getLeadAssignmentList() {
System.out.println("inside lead adsdignment conntroller get mapping");
return leadAssignmentDao.findAll();
}
#GetMapping("/get/{id}")
Optional<LeadAssignment> getLeadAssignment(#PathVariable Long id) {
return leadAssignmentDao.findById(id);
}
#GetMapping("/get/assignto/{assignTo}")
LeadAssignment getLeadAssignmentAssignTo(#PathVariable String assignTo, #RequestParam Map<String, String> params){
System.out.println("Inside start of lead assignment assign to");
System.out.println(params);
LeadAssignment result = leadAssignmentDao.findByAssignTo(assignTo);
return result;
//System.out.println("Inside end of get sales email");
}
#GetMapping("/get/assignby/{assignBy}")
LeadAssignment getLeadAssignmentAssignBy(#PathVariable String assignBy, #RequestParam Map<String, String> params){
System.out.println("Inside start of lead assignment by");
System.out.println(params);
LeadAssignment result = leadAssignmentDao.findByAssignBy(assignBy);
return result;
//System.out.println("Inside end of get sales email");
}
#DeleteMapping("/delete/{id}")
public boolean deleteLeadAssignment(#PathVariable Long id) {
leadAssignmentDao.deleteById(id);
return true;
}
#PutMapping("/update/{id}")
public LeadAssignment updateLeadAssignment(#RequestBody LeadAssignment leadAssignment, #PathVariable Long id) {
System.out.println("Inside lead assignmet update method");
Optional<LeadAssignment> found = leadAssignmentDao.findById(id);
//if(!found.isPresent())
leadAssignment.setId(id);
leadAssignmentDao.save(leadAssignment);
return found.get();
}
/*
#PutMapping("/update/email/{email}")
public LeadAssignment updateLeadAssignmentEmail(#RequestBody User user, #PathVariable String email ) {
System.out.println("inside user email PUT method");
User emailfind = userDao.findByEmail(email);
user.setEmail(email);
userDao.save(user);
return emailfind;
}
*/
/*
#PutMapping("/update/{id}")
public User updateUser(#RequestBody User user) {
return userDao.save(user);
}
*/
#PostMapping("/create")
public LeadAssignment createLeadAssignment(#RequestBody LeadAssignment leadAssignment) {
return leadAssignmentDao.save(leadAssignment);
}
}
My application.properties file :
# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
# Connection url for the database "netgloo_blog"
#spring.datasource.url=jdbc:mysql://localhost:3306/lmsAngularSpring?createDatabaseIfNotExist=true
spring.datasource.url=jdbc:mysql://localhost:3306/lms
# Username and secret
spring.datasource.username=root
spring.datasource.password=test
# Keep the connection alive if idle for a long time (needed in production)
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
# ===============================
# = JPA / HIBERNATE
# ===============================
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager).
# Show or not log for each sql query
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.current_session_context_class = org.springframework.orm.hibernate5.SpringSessionContext
# Hibernate ddl auto (create, create-drop, update): with "update" the database
# schema will be automatically updated accordingly to java entities found in
# the project
#line below was earlier un-commented
#spring.jpa.hibernate.ddl-auto=update
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.open-in-view=false
#spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
Expected postman output:
{
"id": 1,
"remarks": "demo3",
"demanderRentLeadStatus": null,
"demanderBuyLeadStatus": null,
"supplierSellLeadStatus": null,
"supplierRentLeadStatus": null,
"sales": null,
"lastname": "foo",
"firstname": "bar",
"assignTo": "david",
"assignBy": "james",
"requirements": "for rent out"
}
Actual output for postman:
{
"id": 1,
"remarks": "demo3",
"demanderRentLeadStatus": null,
"demanderBuyLeadStatus": null,
"supplierSellLeadStatus": null,
"supplierRentLeadStatus": null,
"sales": null,
"lastName": null,
"firstName": null,
"assignmentTo": null,
"assignmentBy": null,
"requirments": null
}

Rename getter and setter of your class. Make it like:
getter and setter for assignTo are getAssignTo() and setAssignTo(String s) respectively. and do same for assignBy.

Related

Spring Kafka OpenAPI v3 website generation + spec

Small question for Spring Kafka and OpenAPI V3 please.
In the paste, we had a Spring Web MVC web app, very straightforward, type https://interviewnoodle.com/documenting-a-springboot-rest-api-with-openapi-3-7b2bc1605f
package com.openapi.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
#Schema(description = "Book object")
#Entity
#Table(name="books")
public class Book {
#JsonProperty(value="id", required=true, index = 10)
#Schema(description = "Unique identifier of the Book.",
example = "1", required = true)
private long id;
#JsonProperty(value="title", required=true, index = 20)
#Schema(description = "Name of the title.",
example = "Java", required = true)
#NotBlank
#Size(min = 0, max = 20)
private String title;
#JsonProperty(value="author", required=true, index = 30)
#Schema(description = "Name of the author.",
example = "Max Abi", required = true)
#NotBlank
#Size(min = 0, max = 30)
private String author;
public Book() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Column(name = "title", nullable = false)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#Column(name = "author", nullable = false)
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
package com.openapi.controller;
import com.openapi.model.Book;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Collection;
#Tag(name = "book", description = "the book API")
#RequestMapping("/api/v1/books")
public interface BookApi {
#Operation(summary = "Create book", description = "This can only be done by the logged in book.", tags = { "book" })
#ApiResponses(value = { #ApiResponse(description = "successful operation", content = { #Content(mediaType = "application/json", schema = #Schema(implementation = Book.class)), #Content(mediaType = "application/xml", schema = #Schema(implementation = Book.class)) }) })
#PostMapping(value = "/", consumes = { "application/json", "application/xml", "application/x-www-form-urlencoded" })
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Book> postBook(
#NotNull
#Parameter(description = "Created book object", required = true)
#Valid #RequestBody Book body,
#NotNull #Parameter(description = "select which kind of data to fetch", required = true)
#Valid #RequestHeader(value="bookAuthorization", required = true) String bookAuthorization)
throws Exception;
}
For each and every client applications wanting to interact with us, i.e sending the http request with a Book object, we first jumped into a call, wrote a long mail explaning what the Book looks like.
Then, we discovered <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> which changed the game.
We would just do the annotations, generate the Open API v3 doc as we build our project, no extra step.
We would then just send the spec.yml via email, and tell them "please go to http://the-cool-host:8080/v3/api-docs/, and you will be able to see what you should be sending us".
This worked like a charm.
For organizational reason, we changed the http based application to a Kafka based approach.
With that, we lost ability to generate the doc, and also, host it on a website.
What I tried:
I tried leaving the annotations, and running the maven clean install command to generate the doc, but it is not generating anymore.
May I ask what did I miss please?
What are the possibilities to generate the Open API v3 doc on the go, and host it, but for Spring Kafka please?
Thank you

Spring SQL: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "; expected "identifier", when using INSERT INTO

I am working on a Spring Web Application.
I am using mysql database, but for my unit tests, I want to run them in H2 database.
Test specific application properties:
#Specific spring boot configuration for tests
spring.main.banner-mode=off
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:skel;MODE=MYSQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.datasource.user=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create
spring.jpa.defer-datasource-initialization=true
endpoints.enabled=false
# enable test-profile
spring.profiles.active=test
As you can see, my database is in MODE=MYSQL, since my data.sql is in MySQL dialect.
But during initialization of of data.sql I get this error:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement
"INSERT IGNORE INTO [*]user (username, password, first_name, last_name, enabled,
news_cycle, created_at, updated_at) VALUES('admin#example.com',
'$2a$10$BFNo8gUTorQMtikcFbYVEeAPyX5iCn5BpKglp.eJ2DrFs.bNeXgEu', 'Admin', 'Adminoso',
'TRUE', 'NEVER', '2016-01-01 00:00:00', '2016-01-01 00:00:00')"; expected
"identifier"; SQL statement:
INSERT IGNORE INTO user (username, password, first_name, last_name, enabled,
news_cycle, created_at, updated_at) VALUES('admin#example.com',
'$2a$10$BFNo8gUTorQMtikcFbYVEeAPyX5iCn5BpKglp.eJ2DrFs.bNeXgEu', 'Admin', 'Adminoso',
'TRUE', 'NEVER', '2016-01-01 00:00:00', '2016-01-01 00:00:00') [42001-212]
I suppose from this error you can see the SQL statement that is causing the problem, other statements in data.sql do not seem to cause issues. for example:
INSERT IGNORE INTO department (department_id, department_name, created_at,
updated_at) VALUES (1, 'Marketing', '2016-01-01 00:00:00', '2016-01-01 00:00:00');
My user entity:
import at.qe.skeleton.model.facility.Department;
import at.qe.skeleton.model.facility.Room;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Set;
/**
* Entity representing users.
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*/
#Getter
#Setter
#Entity
#EntityListeners(AuditingEntityListener.class)
public class User implements Comparable<User>{
#Id
#Column(name = "username", length = 255)
private String username;
#Column(name = "password",nullable = false,length = 255)
private String password;
#Column(name = "first_Name",nullable = false,length = 255)
private String firstName;
#Column(name = "last_Name",nullable = false,length = 255)
private String lastName;
#Column(name = "enabled", nullable = false)
private boolean enabled;
#Column(name = "news_cycle", nullable = false)
#Enumerated(EnumType.STRING)
private NewsletterCycle newsCycle;
#ElementCollection(targetClass = UserRole.class, fetch = FetchType.EAGER)
#CollectionTable(name = "user_roles")
#Enumerated(EnumType.STRING)
private Set<UserRole> roles;
#ManyToOne
#JoinColumn(name = "department_id")
private Department department;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "manages_department_id", referencedColumnName = "department_id")
private Department managingDepartment;
#ManyToOne
#JoinColumn(name = "assigned_room_id")
private Room assignedRoom;
#OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<Absence> absences;
#CreatedDate
#Column(name = "created_at", nullable = false)
private LocalDateTime created;
#LastModifiedDate
#Column(name = "updated_at", nullable = false)
private LocalDateTime updated;
#Override
public int compareTo(User o) {
return this.username.compareTo(o.getUsername());
}
#Override
public int hashCode() {
int hash = 7;
hash = 59 * hash + Objects.hashCode(this.getUsername());
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof User)) {
return false;
}
final User other = (User) obj;
return Objects.equals(this.username, other.getUsername());
}
#Override
public String toString() {
return "username: " + getUsername() + " name: " + getFirstName() + " " + getLastName();
}
}
What does this "identifier expected" SQL syntax error mean in this case, I can't figure it out.
I tried making all fields except for username in user nullable, and then tried the same statement only inserting with the username, suspecting that maybe some timestamp datatype was the problem, but that did not change a thing.
I hope somebody can help thank you!
You named your table user which is a reserved keyword in H2. It's also a reserved keyword in the ANSI SQL-99 standard and often in other SQL implementations (sometimes it is a non-reserved keyword, for example in MySQL).
You can use reserved keywords as table names in SQL if you delimit them. H2 supports standard identifier delimiters, which are double-quotes.
I don't know if there's an easy way to make Spring delimit the identifiers in SQL statements. I recall it's pretty wonky. You have to define the entity with built-in double-quotes around its name, like this:
#Entity
#Table(name = "\"user\"")
See https://www.chrouki.com/posts/escape-sql-reserved-keywords-jpa-hibernate/
It's easier if you can just avoid using reserved words for your table names (or other identifiers, including columns, procedures, views, indexes, partitions, etc.).
If I understand your question correctly, you are trying to
populate data.sql (mysql format) to an H2 database.
spring.datasource.url=jdbc:h2:mem:skel;MODE=MYSQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
I just doubt that H2 can have enough support on this.
My suggestion is to provide a data-h2.sql for your test. And it will be easy to maintain.

hibernate query in spring boot with H2: Values of types "BOOLEAN" and "INTEGER" are not comparable

I'm trying to run a custom query, and getting a strange (Seemingly unrelated) error.
Error I'm getting is:
o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 90110, SQLState: 90110
Values of types "BOOLEAN" and "INTEGER" are not comparable; SQL statement:
select episode0_.episode_id as episode_1_1_, episode0_.air_date as air_date2_1_, episode0_.episode_number as episode_3_1_, episode0_.name as name4_1_, episode0_.season as season5_1_, episode0_.show_id as show_id7_1_, episode0_.watched as watched6_1_ from episode episode0_ where episode0_.episode_number=(select min(episode1_.episode_number) from episode episode1_ where episode1_.watched=0) and episode0_.season=(select min(episode2_.season) from episode episode2_ where episode2_.watched=0) and episode0_.show_id=? [90110-210]
Things I've tried: native query, and this: Custom Query H2 - Spring Boot
My repository:
import model.Episode;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
public interface EpisodeRepository extends JpaRepository<Episode, Long> {
Optional<Episode> findByEpisodeId(Long episodeId);
#Query("from EPISODE where episodeNumber = (SELECT MIN(episodeNumber) FROM EPISODE WHERE watched is false)" +
" AND season = (SELECT MIN(season) FROM EPISODE WHERE watched is false) " +
" AND show.showId = :showId")
Optional<Episode> findNextUnwatchedEpisode(#Param("showId") Long showId);
}
My entity:
package model;
import lombok.*;
import javax.persistence.*;
import java.util.Date;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
#Entity(name = "EPISODE")
public class Episode {
#Id
#Column(name = "EPISODE_ID", nullable = false)
private Long episodeId;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "SEASON", nullable = false)
private Integer season;
#Column(name = "EPISODE_NUMBER", nullable = false)
private Integer episodeNumber;
#Column(name = "AIR_DATE", nullable = false)
private Date airDate;
#Column(name = "WATCHED", nullable = false)
private boolean watched;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="SHOW_ID")
private TVShow show;
}
application.properties:
spring.datasource.url=jdbc:h2:mem:my_tv;DB_CLOSE_ON_EXIT=FALSE;MODE=Oracle;
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
Hibernate ORM supports H2 2.x.y only since the version 5.6.5.Final. Older versions produce invalid SQL that isn't accepted by new versions of H2 in default configuration.
You need to check version of hibernate-core jar used by your application and update it to a some recent version (current version is 5.6.7.Final).

Select entity with the informed natural id, rather than trying to insert (JPA and SpringBoot)

So basically I have the following need. A person is going to POST an entity called "Expense Report" through a Rest Controller.
That entity has a field Country that is actually an association with another entity.
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "EXPENSE_REPORTS")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class ExpenseReport extends BaseEntity {
#Column(name = "TRIP_DESCRIPTION", nullable = false)
private String tripDescription;
#Column(name = "JUSTIFICATION")
private String justification;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "COUNTRY_ID")
private Country country;
#Column(name = "TRIP_START_DATE")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate tripStartDate;
#Column(name = "TRIP_END_DATE")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate tripEndDate;
#Column(name = "TOTAL_AMOUNT")
private BigDecimal totalAmount;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="USER_ID")
private User user;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="EXPENSE_ITEM_ID")
private Set<ExpenseItem> expenses;
}
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "COUNTRIES")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Country extends BaseEntity {
#Column(name = "NAME")
#NaturalId
private String name;
}
What I wanted is to check, if it is possible that the caller just sends the natural id of the country (in this case, the name) and JPA rather than trying to insert this country, he finds the country with the same name and associates it.
So, rather than sending this post:
{
"tripDescription": "Some trip description...",
"justification": "Some justification...",
"country": {
"id": 12345
}
}
He sends this:
{
"tripDescription": "Some trip description...",
"justification": "Some justification...",
"country": {
"name": "BR"
}
}
I can imagine how to do this either in a repository implementation, but I was wondering if there is something automatic to do this in JPA.
Thanks in advance.
As far as I could see, there is no automatic JPA way of doing this. I know that is not much of an answer, but I also do not think it is likely that there will be a way to do this automatically in JPA (for now), since Hibernate is very focussed on using primary keys and foreign keys (i.e. surrogate IDs).
As "curiousdev" mentions in the comments, using a country code like "BR" (which can be non null unique, just like a primary key) as the key (or joincolumn) is a good alternative in this case. There is a whole discussion about it here if you are interested.
For my own interest, I did some digging in how a repository implementation could look when using the surrogate ID and natural ID. The combination of second level cache and a reference lookup looks promising. You can avoid an extra select-query in that case. The code below is runnable (with the required depencencies in place) and shows what I found so far.
The reference I'm talking about is in the line s.byNaturalId(Country.class).using("code", "NL").getReference();.
The cache is in the settings (hibernate.cache.use_second_level_cache and hibernate.cache.region.factory_class) and the annotations #org.hibernate.annotations.Cache and #NaturalIdCache.
// package naturalid;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.ehcache.jsr107.EhcacheCachingProvider;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NaturalIdCache;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cache.jcache.internal.JCacheRegionFactory;
import org.hibernate.dialect.H2Dialect;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* Using natural ID to relate to an existing record.
* <br>https://stackoverflow.com/questions/60475400/select-entity-with-the-informed-natural-id-rather-than-trying-to-insert-jpa-an
* <br>Dependencies:<pre>
* org.hibernate:hibernate-core:5.4.12.Final
* org.hibernate:hibernate-jcache:5.4.12.Final
* org.ehcache:ehcache:3.8.1
* com.h2database:h2:1.4.200
* org.slf4j:slf4j-api:1.7.25
* ch.qos.logback:logback-classic:1.2.3
* org.projectlombok:lombok:1.18.4
* </pre>
*/
#Slf4j
public class NaturalIdRel {
public static void main(String[] args) {
try {
new NaturalIdRel().test();
} catch (Exception e) {
log.error("Tranactions failed.", e);
}
}
void test() throws Exception {
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#bootstrap
Map<String, Object> settings = new HashMap<>();
// https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#configurations
settings.put("hibernate.dialect", H2Dialect.class.getName());
settings.put("hibernate.connection.url", "jdbc:h2:mem:test;database_to_upper=false;trace_level_system_out=2");
settings.put("hibernate.connection.username", "SA");
settings.put("hibernate.connection.password", "");
settings.put("hibernate.hbm2ddl.auto", "create");
settings.put("hibernate.show_sql", "true");
settings.put("hibernate.cache.use_second_level_cache", "true");
settings.put("hibernate.cache.region.factory_class", JCacheRegionFactory.class.getName());
settings.put("hibernate.cache.ehcache.missing_cache_strategy", "create");
settings.put("hibernate.javax.cache.provider", EhcacheCachingProvider.class.getName());
settings.put("hibernate.javax.cache.missing_cache_strategy", "create");
//settings.put("", "");
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder()
.applySettings(settings)
.build();
Metadata md = new MetadataSources(ssr)
.addAnnotatedClass(ExpenseReport.class)
.addAnnotatedClass(Country.class)
.buildMetadata();
SessionFactory sf = md.getSessionFactoryBuilder()
.build();
try {
createCountry(sf);
createExpense(sf);
} finally {
sf.close();
}
}
void createCountry(SessionFactory sf) {
Country c = new Country();
c.setCode("NL");
try (Session s = sf.openSession()) {
save(s, c);
}
}
void createExpense(SessionFactory sf) {
ExpenseReport er = new ExpenseReport();
er.setDescription("Expenses");
er.setReason("Fun");
// Watch (log) output, there should be no select for Country.
try (Session s = sf.openSession()) {
// https://www.javacodegeeks.com/2013/10/natural-ids-in-hibernate.html
Country cer = s.byNaturalId(Country.class).using("code", "NL").getReference();
er.setCountry(cer);
save(s, er);
}
}
void save(Session s, Object o) {
Transaction t = s.beginTransaction();
try {
s.save(o);
t.commit();
} finally {
if (t.isActive()) {
t.rollback();
}
}
}
#Entity
#Data
static class ExpenseReport {
#Id
#GeneratedValue
int id;
#Column
String description;
#Column
String reason;
#ManyToOne
// Can also directly map country code.
// https://stackoverflow.com/questions/63090/surrogate-vs-natural-business-keys
Country country;
}
#Entity
#Data
// https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/
#org.hibernate.annotations.Cache(
usage = CacheConcurrencyStrategy.READ_WRITE
)
#NaturalIdCache
static class Country {
#Id
#GeneratedValue
int id;
#NaturalId
String code;
}
}
Use association like below, instead of COUNTRIES.COUNTRY_ID use COUNTRIES.NAME
public class ExpenseReport extends BaseEntity {
#Column(name = "TRIP_DESCRIPTION", nullable = false)
private String tripDescription;
#Column(name = "JUSTIFICATION")
private String justification;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = “NAME”,referencedColumnName = "name" ) //Do not use COUNTRY_ID
private Country country;
….
}
public class Country extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "NAME”,nullable = false, updatable = false, unique = true)
#NaturalId(mutable = false)
private String name;
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Country)) {
return false;
}
Product naturalIdCountry = (Country) o;
return Objects.equals(getName(), naturalIdCountry.getName());
}
#Override
public int hashCode() {
return Objects.hash(getName());
}
}

Spring Boot org.hibernate.exception.ConstraintViolationException

I am getting .ConstraintViolationException when I try to persist data using the POST REST API.
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint
Detail: Failing row contains (null, John Doe, How are you?, I am fine).
I am using #GeneratedValue(strategy = GenerationType.IDENTITY) to auto generate "id" from Hibernate and I am not sure If I am missing any configuration in application.properties. I am using Postgres db.
I tried using GenerationType.AUTO and I was getting hibernate_sequence missing error from postgres.
Thanks!
POST REST API input using Postman
{
"personName": "John Doe",
"question": "How are you?",
"response": "I am fine"
}
questionnaries.sql
CREATE TABLE questionnaries(
id BIGINT PRIMARY KEY,
personName VARCHAR(255) NOT NULL,
question VARCHAR(255) NOT NULL,
response VARCHAR(255) NOT NULL
);
Questionnarie.java #
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
#Entity
#Table(name = "questionnaries")
public class Questionnarie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "personname")
#NotNull
private String personname;
#Column(name = "question")
#NotNull
private String question;
#Column(name = "response")
#NotNull
private String response;
public Questionnarie() {}
public Questionnarie(#NotNull String personname, #NotNull String question, #NotNull String response) {
super();
this.personname = personname;
this.question = question;
this.response = response;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPersonname() {
return personname;
}
public void setPersonname(String personname) {
this.personname = personname;
}
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}}
application.properties
# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
spring.datasource.jndi-name=java:jboss/datasources/test_data_source
# ===============================
# = JPA / HIBERNATE
# ===============================
# Show or not log for each sql query
spring.jpa.show-sql=true
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
That means your database supports sequences for primary key values. So in your case, you will have to create a Database sequence and then use #GeneratedValue(strategy=GenerationType.AUTO) or #GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
#SequenceGenerator(name="seq", sequenceName = "db_seq_name") to generate values for primary key fields.
Also make sure that you add SERIAL to your SQL, so that it looks like: id SERIAL PRIMARY KEY
See the PostgreSQL documentation for the serial data types.
Change your script to:
CREATE TABLE questionnaries(
id BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
personName VARCHAR(255) NOT NULL,
question VARCHAR(255) NOT NULL,
response VARCHAR(255) NOT NULL
);

Categories