How to fix error in SimpleJdbcInsert in SpringBoot application - java

I learn Spring Boot reading book Spring in Action 5. I try to insert data to H2 embedded database.I use SimpleJdbcInsert and executeAndReturnKey method.
Here is a Constructor:
#Autowired
public JdbcOrderRepository(JdbcTemplate jdbc) {
this.orderInserter = new SimpleJdbcInsert(jdbc).withTableName("Taco_Order").usingGeneratedKeyColumns("id");
this.orderTacoInserter = new SimpleJdbcInsert(jdbc).withTableName("Taco_Order_Tacos");
this.objectMapper = new ObjectMapper();
}
Here is methods to insert data:
#Override
public Order save(Order order) {
order.setPlacedAt(new Date());
long orderId = saveOrderDetails(order);
order.setId(orderId);
List<Taco> tacos = order.getTacos();
for (Taco taco : tacos)
saveTacoToOrder(taco, orderId);
return order;
}
private long saveOrderDetails(Order order) {
Map<String, Object> values = objectMapper.convertValue(order, Map.class);
values.put("placedAt", order.getPlacedAt());
long orderId = orderInserter.executeAndReturnKey(values).longValue();
return orderId;
}
The error is on this string:
long orderId = orderInserter.executeAndReturnKey(values).longValue();
Error text:
There was an unexpected error (type=Internal Server Error, status=500).
PreparedStatementCallback; ???????? NULL ?? ????????? ??? ????
"DELIVERYNAME" NULL not allowed for column "DELIVERYNAME"; SQL statement:
INSERT INTO Taco_Order (DELIVERYNAME, DELIVERYSTREET, DELIVERYCITY,
DELIVERYSTATE, DELIVERYZIP, CCNUMBER, CCEXPIRATION, CCCVV, PLACEDAT)
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) [23502-197]; nested exception is
org.h2.jdbc.JdbcSQLException: ???????? NULL ?? ????????? ??? ????
"DELIVERYNAME" NULL not allowed for column "DELIVERYNAME"; SQL statement:
INSERT INTO Taco_Order (DELIVERYNAME, DELIVERYSTREET, DELIVERYCITY,
DELIVERYSTATE, DELIVERYZIP, CCNUMBER, CCEXPIRATION, CCCVV, PLACEDAT)
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) [23502-197]
Bur in debugger evrything is alright:
value parameter is fully loaded by values.
idea screensot
Class Order:
#Data
public class Order {
#NotBlank(message = "Name is required")
private String name;
#NotBlank(message = "Name is required")
private String street;
#NotBlank(message = "Name is required")
private String city;
#NotBlank(message = "Name is required")
private String state;
#NotBlank(message = "Name is required")
private String zip;
#CreditCardNumber(message = "Not valid cc")
private String ccNumber;
#Pattern(regexp = "^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$", message = "Must be formatted MM/YY")
private String ccExpiration;
#Digits(integer = 3, fraction = 0, message = "Invalid CVV")
private String ccCVV;
private Long id;
private Date placedAt;
List<Taco> tacos = new ArrayList<>();
public void addDesign(Taco design) {
this.tacos.add(design);
}
}
How to fix this problem and insert data to H2 database using SimpleJdbcInsert?

I faced same issue today. I was able to resolve this.
To fix this remove, go to schema.sql(file in which you are creating tables) and remove delivery from fields in Taco_Order table.
From book
SimpleJdbcInsert has a couple of useful methods for executing the insert: execute() and executeAndReturnKey(). Both accept a Map, where the map keys correspond to the column names in the table the data is inserted into. The map values are inserted into those columns.
Keys name are fields from Order.java

I went through the screen shot and i do not see any value for column DELIVERYNAME.
The issue is because of the table in which you are running the insert operation doesn't allow null values for the column DELIVERYNAME.
Ways to solve this:-
Remove the NOT NULL constraints from the column in the table definition.
Remove the #NotBlank annotation from the members of the object if you are not sure whether the data is going to be blank or not.
Use proper mapping between the column names in table and those with the member name in Class/Entity Definition. You can use #Column annotation to easily do that.
As much I can understand following your code and screenshot, that the issue is because of improper mapping between column names and that of the member of the object.
It is recommended that rather than relying on the auto mapping of Spring Boot coder should manually do the mapping using #Column annotation or mapping through hibernate configuration file.

The issue is with attributes name in Order class. These should mirror the coloumn name of respective table.
Eg:
private String deliveryName; -> would be mapped to deliveryName field in db.
The issue has arisen as in schema.sql the field is named as "deliveryName" while in ingredient class the attirbute is named "name".

This answer is for those who follow the examples from book "Spring in action". You won't do such thing in real life, but if you're a reader of this book it is reasonable to replace this:
Map<String, Object> values = objectMapper.convertValue(order, Map.class);
values.put("placedAt", order.getPlacedAt());
with this:
Map<String,Object> values = new HashMap<>();
values.put("ID", order.getId());
values.put("PLACEDAT", order.getPlacedAt());
values.put("DELIVERYNAME", order.getName());
values.put("DELIVERYSTREET", order.getStreet());
values.put("DELIVERYCITY", order.getCity());
values.put("DELIVERYSTATE", order.getState());
values.put("DELIVERYZIP", order.getZip());
values.put("CCNUMBER", order.getCcNumber());
values.put("CCEXPIRATION", order.getCcExpiration());
values.put("CCCVV", order.getCcCVV());
Previous answers mentioned changing schema which will cause problems if you will follow examples from next chapters or using jpa annotations to perform mapping, but according to the author of the book you don't know about them because they are introduced in the next chapters. Besides you won't use JdbcOrderRepository class from the next chapter.

Names of fields in Order class must be the same as columns names in table Taco_Order.
#Data
public class Order {
private Long id;
private Date placedAt;
#NotBlank(message = "Podanie imienia i nazwiska jest obowiazkowe")
private String deliveryName;
#NotBlank(message = "Podanie ulicy jest obowiązkowe")
private String deliveryStreet;
#NotBlank(message = "Podanie miasta jest obowiązkowe")
private String deliveryCity;
#NotBlank(message = "Podanie województwa jest obowiązkowe")
private String deliveryState;
#NotBlank(message = "Podanie kodu pocztowego jest obowiązkowe")
private String deliveryZip;
#CreditCardNumber(message = "To nie jest prawidłowy numer karty kredytowej")
private String ccNumber;
#Pattern(regexp = "^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$", message = "Wartość musi być w formacie MM/RR")
private String ccExpiration;
#Digits(integer = 3, fraction = 0, message = "Nieprawidłowy kod CVV")
private String ccCVV;
public List<Taco> tacos = new ArrayList<>();
public void addDesign(Taco design) {
this.tacos.add(design);
}
public List<Taco> getTacos() {
return tacos;
}
}

I encountered the same problem today and all I had forgotten to do was to update the fields in the order.html file to deliveryName, deliveryCity etc.

Related

only select columns of the a psql table could receive data

Something very bizarre have been happening. I have a very simple Entity recipe like so
#Data
#Entity
#Table(name = "recipe", schema = "public")
#AllArgsConstructor
#NoArgsConstructor
public class Recipe {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "name")
private String name;
#Column(name = "instructions")
private String instructions;
#Column(name = "date_added", nullable = false)
private String dateAdded;
#Column(name = "last_edited", nullable = false)
private String lastEdited;
}
and I have this post service that should post the 4 string attribute to the database
public void postRecipe(Recipe recipe){
var sql = """
INSERT INTO public.recipe ("name","instructions","date_added","last_edited")
VALUES (?, ?, ?, ?)
""";
jdbcTemplate.update(
sql,
recipe.getName(),
recipe.getInstructions(),
recipe.getDateAdded(),
recipe.getLastEdited()
);
}
However when the following jason is sent using postman, I get the null value error.
{
"name":"test",
"instructions":"don't eat",
"date_added":"03/04/2017",
"last_edited":"03/04/2017"
}
ERROR: null value in column \"date_added\" of relation \"recipe\" violates not-null constraint\n Detail: Failing row contains (3, null, don't eat, null, test)
The strangest thing is that only the "name" and "instruction" columns receive their data and not the other columns. I have tried adding another String attribute to the Entity class and it also cannot get data from the jason.
Edit 1:
I have tried adding the data directly through pgadmin and it worked fine
INSERT INTO recipe (name, instructions, date_added, last_edited)
VALUES ('test', 'test instruction', '2020/03/05', '2020/05/08');
It looks like your deserialization is broken - transforming your JSON into the Java entity, which results in some null values present. Most likely because date_added != dateAdded (field name), and Jackson cannot properly set a value.
I recommend having a look at Jackson guide: https://www.baeldung.com/jackson-annotations, #JsonProperty specifically. And overall do not mix entities and DTOs
After many trials and errors I was able to come up with a solution but still have no clue as to why this is happening. It turns out the under score in the annotation is the problem.
//won't work
#Column(name = date_added)
//works
#Column(name = dateadded)
This is pretty strange because I am fairly certain that the under score is generated by hibernate.
if anyone know why this is happening please let me know... for now I will just stay away from the under scrolls.

DataException: could not execute query

I get this error when I try to run start my application:
org.springframework.dao.DataIntegrityViolationException: could not execute query; SQL [SELECT * FROM testquestions ORDER by id DESC LIMIT 1]; nested exception is org.hibernate.exception.DataException: could not execute query
As seen in previous problems on StackOverflow, I tried to adjust the length of my data input in my sql file and I've set the length of my #Column to the same amount of characters. this didn't help.
this is my #Table class:
#Entity
#Getter
#Setter
#Table(name = "testquestions")
public class TestQuestion {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "questiontitle", length = 2000)
private String questionTitle;
#Column(name = "info", length = 4096)
private String Info;
#Column(name = "solvetime")
private int solveTime;
#Column(name = "difficultylevel")
private DifficultyLevel difficultyLevel;
#Column(name = "questionimage")
private Image questionImage;
public TestQuestion(){
}
public TestQuestion(int id, String questionTitle, String info, DifficultyLevel difficultyLevel) {
this.id = id;
this.questionTitle = questionTitle;
Info = info;
this.difficultyLevel = difficultyLevel;
}
public String getInfo() {
return Info;
}
}
This is my # Query in my QuestionRepository:
#Query(value = "SELECT * FROM testquestions ORDER by id DESC LIMIT 1", nativeQuery = true)
TestQuestion fetchLastQuestion();
This is my database.sql file, it writes to a PostgreSQL data base:
TRUNCATE TABLE users CASCADE;
TRUNCATE TABLE testquestions CASCADE;
DROP TABLE users;
DROP TABLE testquestions;
CREATE TABLE users(
id int,
username varchar(255),
password varchar(255),
role varchar(255)
);
CREATE TABLE testquestions(
id int primary key ,
questiontitle varchar(2000),
info varchar(4096),
solvetime int,
difficultylevel varchar(255),
questionimage bytea
);
INSERT INTO users(id, username, password, role)
VALUES (0, 'user', 'u', 'user'),
(1, 'user','u','user');
INSERT INTO testquestions(id,questiontitle, info, solvetime, difficultylevel, questionimage)
VALUES (0, 'Multiple Databases', 'A company wants to use Spring Boot in a web application which should use JPA as a database abstraction. The unit tests should be run on an H2 database while the production should run on a MySQL database.
Select all the things that need to be done or that will be done automatically by Spring Boot.', 3, 'Easy',
''),
(1, 'Screen Orientation', 'Which of these methods are called when the screen changes orientation from portrait to landscape in Android?',
3, 'Easy',''),
(2, 'Merge Names', 'Implement the uniqueNames method. When passed two arrays of names, it will return an array containing the names that appear in either or both arrays. The returned array should have no duplicates.
For example, calling MergeNames.uniqueNames(new String[]{''Ava'', ''Emma'', ''Olivia''}, new String[]{''Olivia'', ''Sophia'', ''Emma''}) should return an array containing Ava, Emma, Olivia, and Sophia in any order.',
10, 'Easy',''),
(3, 'Date', 'Write a function that converts user entered date formatted as M/D/YYYY to a format required by an API (YYYYMMDD). The parameter "userDate" and the return value are strings.
For example, it should convert user entered date "12/31/2014" to "20141231" suitable for the API.', 10, 'Easy', ''),
(4, 'Inspector', 'Fix the bugs in the following HTML code.', 10, 'Easy',''),
(5, 'Train Composition', 'A TrainComposition is built by attaching and detaching wagons from the left and the right sides, efficiently with respect to time used.
For example, if we start by attaching wagon 7 from the left followed by attaching wagon 13, again from the left, we get a composition of two wagons (13 and 7 from left to right). Now the first wagon that can be detached from the right is 7 and the first that can be detached from the left is 13.
Implement a TrainComposition that models this problem.', 20, 'Hard', '');
Has anyone got an idea how to fix this error?
Thanks!
Tom

ORA_02201 Sequence is not allowed here

hope you all doing well. I'm continuously getting "ORA_02201 - Sequence is not allowed here" SQL syntax error when I'm trying to post new entry to Database using JSON. I'm using Oracle Autonomous Database
In Oracle Database, folks use GenerationType.SEQUENCE strategy for ID (Primary Key) increment.
I've tried something beforehand, let me share with you. It seems root of the issue is that Sequence is not working with JSON, or not set up to be working. It gives "Sequence is not allowed here" when my JSON request doesn't have id, so when Sequence is supposed to work.
Note that, I have this app as MVC web application where I save entity into database through getting data from form input in jsp pages. It works flawlessly and generate ID correctly. Problem is with JSON I believe.
{
"firstName" : "Mark",
"lastName" : "Heiberg",
"email" : "heibergmark#dot.com"
}
I made request by specifying the Id in JSON which id exists in database, then it updated the entry. So I get assured that root of the cause is id generation, when it should make new id, it doesn't work. I couldn't find anything about JSON-Oracle DB relationship in Internet. I wanted to ask here, maybe you know can I or should I do something to enable support for Sequence generation type on the moment of JSON request. Thanks in advance.
#PostMapping("/customers")
Customer addCustomer(#RequestBody Customer customer){
customer.setId(0); //set id to 0 to force it to add customer to DB, instead of updating current.
customerService.saveCustomer(customer);
return customer;
}
This is Table Definition SQL: (I've built it using Oracle APEX environtment, don't mind that 9999999999999999, it's by default)
CREATE TABLE "CUSTOMER"
( "ID" NUMBER(3,0) GENERATED BY DEFAULT AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
"FIRST_NAME" VARCHAR2(15) COLLATE "USING_NLS_COMP",
"LAST_NAME" VARCHAR2(15) COLLATE "USING_NLS_COMP",
"EMAIL" VARCHAR2(30) COLLATE "USING_NLS_COMP",
CONSTRAINT "CUSTOMER_PK" PRIMARY KEY ("ID")
USING INDEX ENABLE
) DEFAULT COLLATION "USING_NLS_COMP"
/
CREATE OR REPLACE EDITIONABLE TRIGGER "BI_CUSTOMER"
before insert on "CUSTOMER"
for each row
begin
if :NEW."ID" is null then
select "CUSTOMER_SEQ".nextval into :NEW."ID" from sys.dual;
end if;
end;
/
ALTER TRIGGER "BI_CUSTOMER" ENABLE
/
This is my Entity class where Database annotations is implemented:
package com.customer_tracker.entity;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
#Entity
#Table(name = "CUSTOMER")
public class Customer {
public Customer() {
}
public Customer(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Customer_SEQ")
#SequenceGenerator(name = "Customer_SEQ", sequenceName = "CUSTOMER_SEQ", allocationSize = 1)
#Column(name = "ID")
private int id;
#NotNull(message = "This field is required!")
#Size(min = 3, max = 15, message = "Write something!")
#Column(name = "FIRST_NAME")
private String firstName;
#NotNull(message = "This field is required!")
#Size(min = 3, max = 15, message = "Write something!")
#Column(name = "LAST_NAME")
private String lastName;
#NotNull(message = "This field is required!")
#Size(min = 3, max = 30, message = "Write something!")
#Column(name = "EMAIL")
private String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName.trim().replaceAll("\\s", "");
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName.trim().replaceAll("\\s", "");
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email.trim().replaceAll("\\s", "");
}
#Override
public String toString() {
return "id = " + id +
", firstName = " + firstName + ", lastName = " + lastName + ", email = " + email;
}
}
This is the code snippet where Hibernate going to save entry to Database:
package com.customer_tracker.dao;
import com.customer_tracker.entity.Customer;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public class CustomerList implements CustomerDAO{
#Autowired
private SessionFactory sessionFactory;
#Override
public void saveCustomer(Customer customer) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(customer);
}
}
... GET, PUT, DELETE methods work as desired, only POST doesn't work as database should generate id behind the scenes.
The problem is that you are using an identity column with the option generated always, but at the same time you are using a trigger to populate the column ID with a sequence. You can't do that, either you use a sequence and a trigger, or you use identity columns. If you are in Oracle 12c or higher, I strongly recommend you to use identity columns, unless some of the restrictions they inherit may affect the way your application behaves.
See this example
SQL> create table test_col ( c1 number generated always as identity start with 1 increment by 1 , c2 varchar2(1) ) ;
Table created.
SQL> insert into test_col ( c2 ) values ( 'A' ) ;
1 row created.
SQL> select * from test_col ;
C1 C
---------- -
1 A
SQL> insert into test_col ( c1,c2 ) values ( 2,'B' ) ;
insert into test_col ( c1,c2 ) values ( 2,'B' )
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column
You have to drop the trigger and the sequence as they are useless in this scenario. The error is surely raised when the trigger is fired. When you use identity columns, Oracle handles automatically for you the sequence and the insert.
The identity columns are subject to the following restrictions:
Each table has one and only one identity column.
The data type of the identity column must be a numeric data type. the user-defined data type is not allowed to use with the identity clause.
The identity column is not inherited by the CREATE TABLE AS SELECTstatement.
The identity column cannot have another DEFAULT constraint.
The encryption algorithm for encrypted identity columns can be inferred therefore you should use a strong encryption algorithm.
The inline constraint of the identity column must not conflict with the NOT NULL and NOT DEFERRABLE constraint stated by the identity clause.
generated always is one of the options for Identity Columns. Read more about that in the link below
Identity Columns
if you are using JPA you should have #Id and #GeneratedValue (with proper strategy) annotations over id field and it will be auto-generated.
Example: https://spring.io/guides/gs/accessing-data-jpa/

Token SQL92 not supported

I have some troubles in my app using sqlRestriction method:
This is my movie class:
public class Movie {
#Id
#Column(name = "pk")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "movie_seq")
#SequenceGenerator(name = "movie_seq", sequenceName = "movie_seq", allocationSize = 1, initialValue = 1)
public long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Column(name = "name")
public String str_name = "";
...
}
When i do something like that (dummy test):
List<Movie> movies = session.createCriteria(Movie.class)
.add(Restrictions.sqlRestriction("1 in (1)"))
.list();
It Works perfectly.
My second test (changing the Restriction):
.add(Restrictions.sqlRestriction("Movie.id in (166,171)"))
And my third test
add(Restrictions.sqlRestriction("{Movie}.id in (166,171)"))
Both of them fails in the same way:
ERROR: Token SQL92 no soportado en la posición: 345
Exception in thread "main" org.hibernate.exception.GenericJDBCException: could not extract ResultSet
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
...
Caused by: java.sql.SQLException: Token SQL92 no soportado en la posición: 345
at oracle.jdbc.driver.OracleSql.handleODBC(OracleSql.java:1275)
...
What am I doing wrong?
Edited
I tried :
.add(Restrictions.sqlRestriction("{alias}.id in (?, ?)", new Long[]{166L,171L}, new Type[]{Hibernate.Long, Hibernate.Long}) )
And i got this : "Long cannot be resolved or is not a field"
It seems "Hibernate.{AnyType}" is deprecated, see this link: Why Hibernate STRING can not be resolved?
i tried this instead:
.add(Restrictions.sqlRestriction("{alias}.id in (?, ?)", new Long[]{166L,171L}, new Type[]{LongType.INSTANCE, LongType.INSTANCE}) )
But I got this error:
Caused by: java.sql.SQLSyntaxErrorException: ORA-00904: "THIS_"."ID": invalid identifier
Not sure if you use the alias correctly. Try following
Restrictions.sqlRestriction("{alias}.pk in (?, ?)", new Long[]{166L,171L}, new Type[]{Hibernate.Long, Hibernate.Long}) )
Count of ? needs to the same as the size of given array so the (?,?) should be constructed & appended dynamically based on the array passed. As well as the type array.
EDIT: the id field name is not id but pk, missed #Column(name = "pk") in origal answer. Newer hibernate versions have no more 'Hibernate.Long' but use LongType.INSTANCE instead.
Restrictions.sqlRestriction("{alias}.pk in (?, ?)", new Long[]{166L,171L}, new Type[]{LongType.INSTANCE, LongType.INSTANCE}) )
Solved.
I was using the name of the hibernate table/field names (I mean "id") it was changed to "pk". and it worked!.
.add(Restrictions.sqlRestriction("pk in (?, ?)", new Long[]{166L,171L}, new Type[]{LongType.INSTANCE, LongType.INSTANCE}) )
I thought I have to use the HQL table/field names. But the names/fields of the phisicall table is the correct!.

Update counter in DB using JPA, avoiding manual synchronization

I have a Counter entity as below : for each prefix (current year and month), I am maintaining a counter that I need to increment.
#Entity
#Table(name = "T_COUNTER")
#SequenceGenerator(allocationSize = 1, name = "S_COUNTER", sequenceName = "S_COUNTER")
public class CodeCounter implements Serializable {
private static final long serialVersionUID = 6431190800245592165L;
#Id
#GeneratedValue(strategy = SEQUENCE, generator = "S_COUNTER")
#Column(name = "ID")
private Long id;
#Column(name = "PREFIX", nullable = false,unique = true)
private String prefix;
#Column(name = "COUNTER", nullable = false)
private Integer counter;
This is my very simple JPA repository, using Spring data :
public interface CodeCounterRepository extends JpaRepository<CodeCounter, Long> {
#Transactional
CodeCounter findByPrefix(String prefix);
}
Now, whenever my service is called, I need to get the right counter thanks to the prefix (and create it if it doesn't exist yet, the first time of the month), increment and save back. This is how I have implemented it so far :
//entry point in the service
public String generateUniqueRequestCode() {
System.out.println("Generating new Request Code for Consolidated Request");
Integer counter = getUniqueCodeAndUpdateCounter(calendarProvider);
String requestCode = format("%s_%04d_%02d_%06d", REQUEST_CODE_PREFIX, calendarProvider.getCurrentYear(), calendarProvider.getCurrentMonth(),
counter);
System.out.println("New Request Code for Consolidated Request:: " + requestCode);
return requestCode;
}
#Transactional
private synchronized Integer getUniqueCodeAndUpdateCounter(CalendarProvider calendarProvider) {
System.out.println("entering..");
String prefix = format("%04d_%02d", calendarProvider.getCurrentYear(), calendarProvider.getCurrentMonth());
CodeCounter codeCounter = codeCounterRepository.findByPrefix(prefix);
if (codeCounter != null) {
codeCounter.setCounter(codeCounter.getCounter() + 1);
} else {
codeCounter = new CodeCounter();
codeCounter.setPrefix(prefix);
codeCounter.setCounter(1);
}
CodeCounter counter=codeCounterRepository.save(codeCounter);
int result=counter.getCounter();
System.out.println("..exiting");
return result;
}
I've added a multithreaded unit test (using tempus fugit library ) with H2 DB, that shows it's working when 2 threads try to generate a unique code at the same time, but I'm not too happy with my code : I would like to get rid of that synchronized method and rely solely on proper transaction configuration.
If I remove the synchronized keyword, then both threads execute the method at same time and it fails because they generate the same prefix, which shouldn't happen (Unique index or primary key violation). Here's the log :
Generating new Request Code for Consolidated Request
Generating new Request Code for Consolidated Request
entering..
entering..
Hibernate: select codecounte0_.id as id1_4_, codecounte0_.counter as counter2_4_, codecounte0_.prefix as prefix3_4_ from t_counter codecounte0_ where codecounte0_.prefix=?
Hibernate: select codecounte0_.id as id1_4_, codecounte0_.counter as counter2_4_, codecounte0_.prefix as prefix3_4_ from t_counter codecounte0_ where codecounte0_.prefix=?
Hibernate: call next value for S_COUNTER Hibernate: call next value for S_COUNTER
Hibernate: insert into t_counter (counter, prefix, id) values (?, ?, ?)
Hibernate: insert into t_counter (counter, prefix, id) values (?, ?, ?)
..exiting
New Request Code for Consolidated Request:: CR_2016_09_000001
17:02:39.853 WARN SqlExceptionHelper - SQL Error: 23505, SQLState: 23505
17:02:39.854 ERROR SqlExceptionHelper - Unique index or primary key violation...
Any idea of how to implement this without synchronizing myself in the code ?
Is writing a method in your entity that is annotated with #PrePersist or #PreUpdate something that you are looking for?

Categories

Resources