Why Spring boot JPA native update is giving PSQLException with postgres Database? - java

I have this Entity class:
#Entity
#Table(name = "inbox_inbox")
#Getter
#Setter
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Inbox implements Serializable {
#Id
private int id;
#Column(name = "created")
private Date created;
#Column(name = "modified")
private Date modified;
#Column(name = "status")
private String status;
}
I have this repository:
#Repository
public interface InboxRepository extends JpaRepository<Inbox, Integer> {
List<Inbox> findInboxesByStatus(String status);
#Modifying
#Transactional
#Query(value = "update inbox_inbox i set i.status = ?2 where i.id = ?1", nativeQuery = true)
int setInboxStatusById(int id, String status);
}
If I call findInboxesByStatus(String status) with required status, then it gives the expected result. But when calling setInboxStatusById() then it is giving me an exception!
I am giving my calling part here:
int updatedRows = inboxRepository.setInboxStatusById(2, "processing");
And getting this exception:
2020-02-10 22:21:57.486 DEBUG 7 --- [main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.setInboxStatusById]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-02-10 22:21:57.486 DEBUG 7 --- [main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1663686815<open>)] for JPA transaction
2020-02-10 22:21:57.486 DEBUG 7 --- [main] o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
2020-02-10 22:21:57.486 DEBUG 7 --- [main] o.h.e.t.internal.TransactionImpl : begin
2020-02-10 22:21:57.486 DEBUG 7 --- [main] org.postgresql.jdbc.PgConnection : setAutoCommit = false
2020-02-10 22:21:57.486 DEBUG 7 --- [main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#10fc01e0]
2020-02-10 22:21:57.487 DEBUG 7 --- [main] org.hibernate.SQL : update inbox_inbox i set i.status = ? where i.id = ?
2020-02-10 22:21:57.487 DEBUG 7 --- [main] o.h.engine.jdbc.spi.SqlExceptionHelper : could not execute statement [n/a]
org.postgresql.util.PSQLException: ERROR: column "i" of relation "inbox_inbox" does not exist
Position: 26
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2497) ~[postgresql-42.2.8.jar!/:42.2.8]
at .....
Why getting this? I searched for some solution in this site as well. But it seems, it is a new thread. So asking for help. Thanks in advance.

Using aliases in the update query is not allowed. Please, use the following:
#Modifying
#Transactional
#Query(value = "update inbox_inbox set status = ?2 where id = ?1", nativeQuery = true)
int setInboxStatusById(int id, String status);

Related

Spring / Hibernate - StaleStateException thrown when deleting entity

One of my entities is throwing a StaleStateException when trying to delete it. Code in question:
#RestController
#RequestMapping("/cycles")
public class DeleteCycleController {
#DeleteMapping("{cycleId}")
#ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteCycleById(#PathVariable("cycleId") UUID cycleId, Principal principal) {
// cycleRepository extends CrudRepository
cycleRepository.deleteById(new CycleId(cycleId));
// deleter.deleteCycle(new CycleId(cycleId));
}
}
Throws the following:
org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: HikariProxyPreparedStatement#716092925 wrapping delete from cycle where id='c4c1428e-c296-4199-85f6-8ef16e6999c9'::uuid; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: HikariProxyPreparedStatement#716092925 wrapping delete from cycle where id='c4c1428e-c296-4199-85f6-8ef16e6999c9'::uuid
Here are the hibernate logs just before the failure:
2021-03-11 20:34:11.532 DEBUG 55483 --- [nio-8080-exec-7] org.hibernate.SQL : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : select cycle0_.id as id1_0_0_, cycle0_.attributes as attribut2_0_0_, cycle0_.created_at as created_3_0_0_, cycle0_.cycle_type_id as cycle_ty4_0_0_, cycle0_.description as descript5_0_0_, cycle0_.end_time as end_time6_0_0_, cycle0_.start_time as start_ti7_0_0_, cycle0_.end_location as end_loca8_0_0_, cycle0_.last_modified_at as last_mod9_0_0_, cycle0_.name as name10_0_0_, cycle0_.organization_id as organiz11_0_0_, cycle0_.start_location as start_l12_0_0_ from cycle cycle0_ where cycle0_.id=?
Hibernate: select cycle0_.id as id1_0_0_, cycle0_.attributes as attribut2_0_0_, cycle0_.created_at as created_3_0_0_, cycle0_.cycle_type_id as cycle_ty4_0_0_, cycle0_.description as descript5_0_0_, cycle0_.end_time as end_time6_0_0_, cycle0_.start_time as start_ti7_0_0_, cycle0_.end_location as end_loca8_0_0_, cycle0_.last_modified_at as last_mod9_0_0_, cycle0_.name as name10_0_0_, cycle0_.organization_id as organiz11_0_0_, cycle0_.start_location as start_l12_0_0_ from cycle cycle0_ where cycle0_.id=?
2021-03-11 20:34:11.533 TRACE 55483 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : binding parameter [1] as [OTHER] - [c4c1428e-c296-4199-85f6-8ef16e6999c9]
2021-03-11 20:34:11.542 DEBUG 55483 --- [nio-8080-exec-7] org.hibernate.SQL : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : delete from cycle where id=?
Hibernate: delete from cycle where id=?
2021-03-11 20:34:11.542 TRACE 55483 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : binding parameter [1] as [OTHER] - [c4c1428e-c296-4199-85f6-8ef16e6999c9]
And the Cycle entity:
#javax.persistence.Entity
#Accessors(fluent = true)
#Getter
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
#TypeDefs({#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)})
public class Cycle {
#Id private UUID id;
private CycleName name;
private CycleDescription description;
private UUID cycleTypeId;
private UUID organizationId;
private Instant createdAt;
private Instant lastModifiedAt;
#Type(type = "jsonb")
private Map<String, Object> attributes;
private CycleDuration duration;
#Column(columnDefinition = "Geometry", nullable = true)
private Geometry startLocation;
private Geometry endLocation;
#OneToMany(mappedBy = "cycleId", cascade = CascadeType.ALL, orphanRemoval = false)
#Getter(AccessLevel.PRIVATE)
#Setter(AccessLevel.PUBLIC)
private List<CycleEntityMapping> entities = new ArrayList<>();
...
}
Any ideas as to what could be going wrong? Another (simpler) entity deletes fine.
Not sure if its the OneToMany causing issues, but I've tried changing the cascade type, orphan removal and fetch type with no luck. Otherwise, could it be caused by the value objects? Note that updating the entity works fine - it's only failing on delete.
In this case, it was caused by a bug in a before delete trigger. There was a bug in the trigger causing the row to not be deleted:
CREATE OR REPLACE FUNCTION trigger_delete_row_backup()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO deleted_cycle values (OLD.*);
RETURN NEW;
-- should actually be RETURN OLD
END;
$BODY$
language PLPGSQL;
Once the trigger was fixed, the issue no was resolved.

Hibernate binding returns null on insert

I'm here to get help because I've been searching the internet for a few hours without finding my answers. Maybe someone here will have a solution to my problem.
I'm new with spring boot, so don't be too mean to me, I probably missed some information in the documentation.
I'm trying to add data to my database with a json file that looks like this one.
Everything works correctly up to the OneToMany relationship and vice versa ManyToOne (I followed the doc here).
I put Cascade.ALL to propagate to entities.
As you can see, Hibernate makes a binding with a null value, as if it had never been inserted whereas it seems to be at the previous step.
A lot of topics deal with this problem, and for many of them they have been solved with the null = false option, but it doesn't work for me.
Information:
A contact can have multiple email addresses, and an email address belongs to only one contact.
My code :
Emails
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "Email")
public class Email implements Serializable {
#Id
#GeneratedValue
#Column(name = "idEmail")
private int id;
#Column(name = "Email")
#JsonProperty("mail")
private String email;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Contact_idContact")
private Contact contact;
}
Contact
#Entity
#Table(name = "Contact")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
public class Contact implements Serializable {
#GeneratedValue
#Id
#Column(name = "idContact", nullable = false, unique = true)
private int id;
#Column(name = "Nom")
private String nom;
#Column(name = "Prenom")
private String prenom;
#Column(name = "Mobile")
private String mobile;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "contact")
private Set<Email> emails = new HashSet<>();
}
My json post :
{
"contacts": [
{
"nom": "myName",
"prenom": "MyFirstName",
"mobile": "06666666666",
"emails": [
{
"mail": "mail1#test.com"
},
{
"mail": "mail2#test.com"
}
],
}
]
}
POST Method
#PostMapping("/add")
public ResponseEntity<Void> addContact(#RequestBody Contact contact) {
Contact contact1 = contactService.save(contact);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(contact1.getId())
.toUri();
return ResponseEntity.created(location).build();
}
Error:
2020-10-15 16:40:16.501 DEBUG 13608 --- [nio-9000-exec-2] org.hibernate.SQL : insert into Contact (Mobile, Nom, Prenom, idContact) values (?, ?, ?, ?)
2020-10-15 16:40:16.502 TRACE 13608 --- [nio-9000-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [06666666666]
2020-10-15 16:40:16.503 TRACE 13608 --- [nio-9000-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [MyName]
2020-10-15 16:40:16.504 TRACE 13608 --- [nio-9000-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [MyFirstName]
2020-10-15 16:40:16.505 TRACE 13608 --- [nio-9000-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [INTEGER] - [46]
2020-10-15 16:40:16.510 DEBUG 13608 --- [nio-9000-exec-2] org.hibernate.SQL : insert into Email (Contact_idContact, Email, idEmail) values (?, ?, ?)
2020-10-15 16:40:16.511 TRACE 13608 --- [nio-9000-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [INTEGER] - [null]
2020-10-15 16:40:16.512 TRACE 13608 --- [nio-9000-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [mail1#test.com]
2020-10-15 16:40:16.512 TRACE 13608 --- [nio-9000-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [INTEGER] - [47]
2020-10-15 16:40:16.542 WARN 13608 --- [nio-9000-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1048, SQLState: 23000
2020-10-15 16:40:16.542 ERROR 13608 --- [nio-9000-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : Column 'Contact_idContact' cannot be null
2020-10-15 16:40:16.636 ERROR 13608 --- [nio-9000-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
In this case, Contact_idContact should be 46 as inserted.
What's wrong with it?
I resolved my problem. I forgot a CascadeType.ALL in Email.java.

Spring boot test throws JdbcException "table not found"

I have application-test.properties where I defined two datasources
app.datasource.server.url=jdbc:h2:mem:fooserver
app.datasource.server.username=sa
app.datasource.server.password=
app.datasource.server.driverClassName=org.h2.Driver
app.datasource.server.hikari.minimumIdle=5
app.datasource.server.hikari.maximumPoolSize=50
app.datasource.server.hikari.idleTimeout=50000
app.datasource.server.hikari.maxLifetime=55000
app.datasource.manager.url=jdbc:h2:mem:barmanager
app.datasource.manager.username=sa
app.datasource.manager.password=
app.datasource.manager.driverClassName=org.h2.Driver
app.datasource.manager.hikari.minimumIdle=5
app.datasource.manager.hikari.maximumPoolSize=50
app.datasource.manager.hikari.idleTimeout=50000
app.datasource.manager.hikari.maxLifetime=55000
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=create-drop
#logging
logging.level.root=info
logging.file=foobar-rest-test.log
#required for SpringBootTest does not know why
spring.main.allow-bean-definition-overriding=true
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
Each of the datasource requires schema to be available named "foo" this will be created by a schema-fooserver.sql and schema-barmanager.sql in each of these sql scripts the foo schema will be created. Therefore I defined a dataSourceIntializer Bean where I can define which schema-sql file will be loaded.
#Bean(name = "managerDataSourceInitializer")
public DataSourceInitializer dataSourceInitializer1(#Qualifier("managerDataSource") DataSource datasource) {
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
resourceDatabasePopulator.addScript(new ClassPathResource("schema-barmanager.sql"));
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(datasource);
dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator);
return dataSourceInitializer;
}
#Bean(name = "serverDataSourceInitializer")
public DataSourceInitializer dataSourceInitializer1(#Qualifier("serverDataSource") DataSource datasource) {
ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
resourceDatabasePopulator.addScript(new ClassPathResource("schema-fooserver.sql"));
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(datasource);
dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator);
return dataSourceInitializer;
}
During the start of my test the logs show that these schema files have been called and executed.
2019-03-28 15:04:34.252 DEBUG 3124 --- [main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from class path resource [schema-fooserver.sql]
2019-03-28 15:04:34.252 DEBUG 3124 --- [main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: CREATE SCHEMA IF NOT EXISTS FOO
2019-03-28 15:04:34.252 DEBUG 3124 --- [main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from class path resource [schema-fooserver.sql] in 0 ms.
2019-03-28 15:04:34.252 DEBUG 3124 --- [main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from class path resource [schema-barserver.sql]
2019-03-28 15:04:34.252 DEBUG 3124 --- [main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: CREATE SCHEMA IF NOT EXISTS FOO
2019-03-28 15:04:34.252 DEBUG 3124 --- [main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from class path resource [schema-barserver.sql] in 0 ms.
Now when I try to execute my test case it fails because of the following error:
28 15:04:36.035 DEBUG 3124 --- [ main] org.hibernate.SQL : insert into foo.Account (uid, password_hash, login) values (null, ?, ?)
Hibernate: insert into foo.Account (uid, password_hash, login) values (null, ?, ?)
2019-03-28 15:04:36.036 DEBUG 3124 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : could not prepare statement [insert into foo.Account (uid, password_hash, login) values (null, ?, ?)]
org.h2.jdbc.JdbcSQLException: Tabelle "ACCOUNT" nicht gefunden
Table "ACCOUNT" not found; SQL statement:
insert into foo.Account (uid, password_hash, login) values (null, ?, ?) [42102-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
At this point I am trying to create a UserAccount in my testcase
This is the defined UerEntity
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name = "foo.Account")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "uid")
private Long id;
#Column(name = "login")
private String username;
#Column(name = "password_hash")
private String password;
....
Here is the Testcase. The error occurs when the before mehod is called during the createUser Method.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
#AutoConfigureMockMvc
public class UserControllerTest {
private static final Logger LOG = LoggerFactory.getLogger(UserControllerTest.class);
#LocalServerPort
private int port;
TestRestTemplate restTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
#Autowired
private WfcSpringRestApplication controller;
#Autowired
private UserRepository repository;
#Autowired
private MockMvc mvc;
private AuthenticationToken authToken;
#Before
public void before() throws Exception {
headers = new HttpHeaders();
UserEntity user = createTestUser(TEST_ADMIN_USER, TEST_ADMIN_MD5_PW, UserRight.ADMIN);
UserEntity userService = createTestUser(TEST_SERVICE_USER, TEST_ADMIN_MD5_PW, UserRight.SERVICE);
getAuthenticationTokenForTestUser(user);
}
private UserEntity createTestUser(String username, String md5_password, UserRight right) {
UserEntity ue = new UserEntity();
ue.setUsername(username);
ue.setPassword(md5_password);
UserRole roleAdmin = new UserRole();
roleAdmin.setRight(right);
ue.getRoles().put(roleAdmin.getRight(), roleAdmin);
repository.save(ue);
return ue;
}
#Test
public void contextLoads() {
assertThat(controller).isNotNull();
}
In the error message there is the correct table name "could not prepare statement [insert into foo.Account" why it throws the exception that the table account is not found?
I faced similar error and I tried pretty much every solution mentioned on other websites such as DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1; DB_CLOSE_ON_EXIT=FALSE; IGNORECASE=TRUE
But nothing worked for me.
For Spring Boot 2.4+ use spring.jpa.defer-datasource-initialization=true in application.properties (mentioned here - https://stackoverflow.com/a/68086707/8219358)
Another way that worked for me was renaming data.sql to import.sql
I found it here - https://stackoverflow.com/a/53179547/8219358
I realize other solutions are more logical but none of them worked for me and this did.

Hibernate. Many to many giving an "org.hibernate.PersistentObjectException detached entity passed to persist" error

I am new using Spring Boot and Hibernate and I am working on a project that has a simple Many to Many association. A sport Center has many services and viceversa. When i try to enter a json like this to create a new sport center and associate some existing service:
{
"name" : "SC1",
"address" : "Street 1111",
"description" : "A Sport center",
"email" : "sc#sc.com",
"phones" : [{"number" : "123456"}, {"number" : "654321"}],
"services" : [{"idService" : 1}, {"idService" : 2}],
"commune" : {"idCommune" : 1},
"users" : [{"idUser":62}]
}
I am receiving this error:
2018-06-25 03:56:32.670 DEBUG 20696 --- [nio-8080-exec-6] org.hibernate.SQL : insert into sport_center (address, Commune_idCommune, description, email, name) values (?, ?, ?, ?, ?)
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Street 1111]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [1]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [A Sport center]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [sc#sc.com]
2018-06-25 03:56:32.671 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [VARCHAR] - [SC1]
2018-06-25 03:56:32.800 DEBUG 20696 --- [nio-8080-exec-6] org.hibernate.SQL : insert into phone (number, Sport_center_idSport_Center) values (?, ?)
2018-06-25 03:56:32.800 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [123456]
2018-06-25 03:56:32.800 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [252]
2018-06-25 03:56:32.942 DEBUG 20696 --- [nio-8080-exec-6] org.hibernate.SQL : insert into phone (number, Sport_center_idSport_Center) values (?, ?)
2018-06-25 03:56:32.942 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [654321]
2018-06-25 03:56:32.942 TRACE 20696 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [252]
2018-06-25 03:56:33.368 ERROR 20696 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.eiffel.canchai.model.Service; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.eiffel.canchai.model.Service] with root cause
org.hibernate.PersistentObjectException: detached entity passed to persist: com.eiffel.canchai.model.Service
It is important to mention that I have already added some rows in the service table. So, the idea is to add a Sport Center and associate these services to it.
Could you please help me with this?
My classes below:
SportCenter.java
#Entity
#Table(name = "sport_center")
public class SportCenter implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idSport_Center")
private Integer idSportCenter;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "description")
private String description;
#Column(name = "email")
private String email;
#JoinTable(name = "sport_center_has_user", joinColumns = {
#JoinColumn(name = "Sport_center_idSport_Center", referencedColumnName = "idSport_Center")}, inverseJoinColumns = {
#JoinColumn(name = "User_idUser", referencedColumnName = "idUser")})
#ManyToMany(cascade = CascadeType.ALL)
private List<User> users;
#JoinTable(name = "sport_center_has_service", joinColumns = {
#JoinColumn(name = "Sport_center_idSport_Center", referencedColumnName = "idSport_Center")}, inverseJoinColumns = {
#JoinColumn(name = "Service_idService", referencedColumnName = "idService")})
#ManyToMany(cascade = CascadeType.ALL)
private List<Service> services;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "sportCenter")
#JsonIgnore
private List<ImageField> imageFields;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "sportCenter")
#JsonIgnore
private List<Field> fields;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "sportCenter", fetch = FetchType.LAZY)
private List<Phone> phones;
#JoinColumn(name = "Commune_idCommune", referencedColumnName = "idCommune")
#ManyToOne(optional = false)
private Commune commune;
public SportCenter() {
}
Service.java
#Entity
#Table(name = "service")
public class Service implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idService")
private Integer idService;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "services", cascade = CascadeType.ALL)
#JsonIgnore
private List<SportCenter> sportCenters;
public Service() {
}
ServiceDao.java
#Repository
#Transactional
public class ServiceDao implements IServiceDao {
#PersistenceContext
private EntityManager entityManager;
#Override
public void save(Service entity) {
entityManager.persist(entity);
}
The SportCenterDao is almost the same that the above :)
Controller
#Controller
#RequestMapping("/sportcenter")
#CrossOrigin
public class SportCenterController {
#Autowired
private ISportCenterService sportCenterService;
#PostMapping("/register")
public ResponseEntity<?> registerSportCenter(#RequestBody SportCenter sc){
if (sc.getEmail().length() == 0 || sc.getName().length() == 0 || sc.getAddress().length() == 0 || sc.getPhones().size() == 0 || sc.getServices().size() == 0) {
return new ResponseEntity(new ErrorMsg("Check parameters. One of them is missing"),HttpStatus.NO_CONTENT);
}
for (Phone p : sc.getPhones()) {
p.setSportCenter(sc);
}
for (Service s : sc.getServices()) {
s.addSportCenters(sc);
}
sportCenterService.save(sc);
return new ResponseEntity(HttpStatus.OK);
}
I think that probably is an error mapping the associations but I am not sure.
Also, if you see that there is a way to improve my code, please let me know :).. Advices are always good.
I am using Spring Boot V2.0.3 and Hibernate 5.3
Thanks in advance!
The problem here is that you are cascading the persist event from parent to child.
When the child entity has a non-zero ID, then hibernate will assume it already exists.
Since you are getting the data from the rest service, I guess the child "Service" entities are not detached. Hence the error "Detached entity passed to persist".
You need to persist the parent first, make the children managed via merge call, then link the parent to the child entities, or get rid of the Cascade.All.

Does Spring Data JPA ignore #Fetch annotations?

I have the following JPA Mapping (getters and setters out for brevity purposes, the DDL also gets generated from the code which may/may not play a role):
Expense
#Entity
public class Expense {
#Id
#GeneratedValue
private Long id;
private String name;
private Long amount;
private Boolean monthly;
#OneToOne
#JoinColumn(name = "category")
#Fetch(FetchMode.JOIN)
private Category category;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<Label> labels = new ArrayList<>();
//constructor, getters and setters...
}
Category
#Entity
public class Category {
#Id
private String name;
//constructor, getters and setters...
}
Label
#Entity
public class Label {
#Id
private String name;
//constructor, getters and setters...
}
Usage with JpaRepository
So I am using a JpaRepository that looks like this:
public interface ExpensesRepository extends JpaRepository<Expense, Long> {
#Query("SELECT e FROM Expense e LEFT JOIN FETCH e.category")
List<Expense> findAllExpensesExploded();
}
When I use the default findAll() method of the JpaRepository, I get a n+1 select problem:
2017-01-03 19:35:22.665 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select expense0_.id as id1_1_, expense0_.amount as amount2_1_, expense0_.category_name as category5_1_, expense0_.monthly as monthly3_1_, expense0_.name as name4_1_ from expense expense0_
2017-01-03 19:35:22.673 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select category0_.name as name1_0_0_ from category category0_ where category0_.name=?
2017-01-03 19:35:22.674 TRACE 26040 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Rent]
2017-01-03 19:35:22.682 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select category0_.name as name1_0_0_ from category category0_ where category0_.name=?
2017-01-03 19:35:22.683 TRACE 26040 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Insurance]
However, when I use my own findAllExpensesExploded() method I get a single SQL query:
2017-01-03 19:35:22.691 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select expense0_.id as id1_1_0_, category1_.name as name1_0_1_, expense0_.amount as amount2_1_0_, expense0_.category_name as category5_1_0_, expense0_.monthly as monthly3_1_0_, expense0_.name as name4_1_0_ from expense expense0_ left outer join category category1_ on expense0_.category_name=category1_.name
My expectation was for both findAll() and findAllExpensesExploded() to be executed with a single SQL query.
My query worked because I seemed to have constructed it correctly
But why does the findAll() not work with the given mapping annotations? Is it possible that Spring Data is ignoring the #Fetch annotation?
An additional question that seems reasonable to ask is whether the default findAll() should only be used for simple entities? (where simple is defined as no associations).
The Default fetch mode is lazy. It's always a good practice to use #NamedEntityGraph and #EntityGraph annotations when working with Spring Data JPA.You can go through this
whether the default findAll() should only be used for simple entities? (where simple is defined as no associations).
Fetch Mode - LAZY will only fire for primary table. If in the code you call any other method that has a parent table dependency then it will fire Fetch Mode - SELECT.

Categories

Resources