Spring Framework + Eclipselink + Exception translation mechanism - spring

This question is similar to the one asked almost two years ago here. I am looking for some information how to handle exceptions thrown from Eclipselink 2.5 when some common database problems arise. What I am getting is ugly org.springframework.transaction.TransactionSystemException which does not give me any information about what fails. Let's take as an example simple entity:
#Entity
class Insect(_name: String) {
def this() = this(null)
#Id
#Column(unique = true)
var name: String = _name
}
and equally simple repository:
#Repository
#Transactional
class InsectDaoImpl extends InsectDao {
#PersistenceContext
var entityManager: EntityManager = _
override def addInsect(insect: Insect) = entityManager.persist(insect)
}
Executing following code:
insectDao.addInsect(new Insect("hornet"))
insectDao.addInsect(new Insect("hornet"))
Gives me:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "CONSTRAINT_INDEX_8 ON PUBLIC.INSECT(NAME)"; SQL statement:
INSERT INTO INSECT (NAME) VALUES (?) [23505-172]
Error Code: 23505
Call: INSERT INTO INSECT (NAME) VALUES (?)
bind => [1 parameter bound]
Query: InsertObjectQuery(pl.zientarski.model.Insect#1df202be)
Yack! The exception itself does not say anything constructive about the source of the problem. Internal exception is first of all database-specific, secondly only the exception message explains what's wrong. For comparison, the same setup with Hibernate gives this exception:
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY_KEY_8 ON PUBLIC.INSECT(NAME)"; SQL statement:
insert into Insect (name) values (?) [23505-172]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
Exception type instantaneously gives hint about what is going on.
The way Eclipselink works is not a surprise when you look into the source code of org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect.translateExceptionIfPossible(RuntimeException ex) First of all it is not even overridden to support Eclipselink specific exceptions, secondly the default implementation inherited from DefaultJpaDialect expects exceptions deriving from javax.persistence.PersistenceException.
I wanted to ask you what are your best practises for working around this problem. Or maybe I just can't see the solution that is right there. Please let me know your suggestions.
The tests are made with Eclipselink 2.5 and Spring 3.2.3
Thanks.

Related

Hibernate Upgrade to v6 (Spring Boot 3) - "Named type [interface java.util.List] did not implement BasicType nor UserType"

After upgrading to Spring Boot 3 / Hibernate 6, I am getting exceptions during Spring application / test startup.
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration#15ec3c0c testClass = ....
Caused by: jakarta.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.IllegalArgumentException: Named type [interface java.util.List] did not implement BasicType nor UserType
Problem can be traced / debugged down to a List<String> entity property, which is using an AttributeConverter for storing it as a comma-separated string in the (MySQL) DB (TEXT field).
The converter:
#Converter
class StringListToStringConverter : AttributeConverter<List<String>, String> {
....
}
The entity:
#MappedSuperclass
abstract class MyInstance<T>(
...
#Column(name = "iface_ids", columnDefinition = "TEXT")
#Convert(converter = StringListToStringConverter::class)
var interfaceIds: List<String> = emptyList()
...
)
This has been working nicely before with Spring Boot 2.7.x / Hibernate 5.x.
Do you think there is another solution than writing a custom type (and thus removing/replacing the AttributeConverter) in this case?
Kind Regards,
dom
Expecting it to actually work OOTB, the AttributeConverter solution looks so clean&simple for this purpose compared to a custom type implementation that I don't really want to change that.

How do I use Spring Data JPA(Hibernate) ORM mapping to a Oracle Tables from other Schema?

The oracle db is version 12c. I use ojdbc8 connector.
I was only granted access to an account, let's call it schema "USR". Login with USR, I can see tables of another schema, let's call it schema "ADM". There is a table "TGT_TABLE" that I want to map it with JPA inside schema ADM. And under USR console, I am able to query "select * from ADM.TGT_TABLE" to get correct result. Now I write up the Entity class as:
#Data
#Entity
#Table(name = "ADM.TGT_TABLE") // or #Table(name = "TGT_TABLE") , Neither worked
public class ApiHeaderLogs {
#Id
#Column(name = "id")
String id;
....
and my config:
spring.jpa.hibernate.ddl-auto=none
# Oracle settings
spring.datasource.url=jdbc:oracle:thin:#10.119.125.70:1540:dhtlm4
spring.datasource.username=USR
spring.datasource.password=******
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
and my test:
Optional<ApiHeaderLogsEntity> ahl = apiHeaderLogsService.findById(id);
I got error:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
....
Caused by: java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist
and the SQL query in console is:
select ... from adm_tgt_table where ... //#Table(name = "ADM.TGT_TABLE")
Or
select ... from tgt_table where ... //#Table(name = "TGT_TABLE")
Clearly it does not address the table ADM.TGT_TABLE.
How can I map to ADM.TGT_TABLE?
My experience was mainly on MySQL, which has no account related to schema access, and my colleague already proposed a solution using JDBC directly... which I really want to avoid, please let me know if there is a proper way to handle this, thanks
You must define explicitely the table_name and the schema in the #Table annotation
Example
#Table(name="TGT_TABLE", schema="ADM")
See the documentation in javax.persistence.Table
Ommiting the schema assumes the deafult owner of the connecting session, which leads to an error.
Neither can you pass a qualified name (ADM.TGT_TABLE) as a table name.

The stored procedure call with cursors throws invalid column name exception

We have a Spring Boot application where we need to connect to Oracle DB and fetch data via stored procedures. Each of our stored procedure has REF_CURSOR as OUT parameters. I am trying the same using #NamedStoredProcedureQuery and #Entity annotations. We are using ojdbc14.jar in pom.xml and Oracle12cDialect in application.properties file. I get the exception Invalid Column Name while executing my piece of code. Also in the entity class I had to introduce a field with annotation #Id, although there is no such field being returned by the REF_CURSOR of my stored procedure. Can this be a problem? Also not defining #Id field is not an option since Hibernate throws an exception then. Any hints would be highly appreciated.
Implementation and Problem is very similar to the question
Invalid column name exception when calling an Oracle stored procedure with ref_cursor through JPA 2.1
But no answer is posted there
The simple example how you can achieve it:
The database schema.
create table MY_PATIENT
(
PAT_RECID number,
PAT_NAME varchar2(100),
constraint PAT_PK primary key(PAT_RECID)
);
create table MY_ORDER
(
ORD_RECID number,
ORD_CODE varchar2(15),
ORD_PATID number,
constraint ORD_PK primary key(ORD_RECID),
constraint ORD_PAT_FK foreign key(ORD_PATID) references MY_PATIENT(PAT_RECID),
constraint ORD_CODE_UNIQUE unique (ORD_CODE)
);
CREATE OR REPLACE PROCEDURE fetch_patient_orders(
patientId IN NUMBER,
patientOrders OUT SYS_REFCURSOR)
AS
BEGIN
OPEN patientOrders FOR
SELECT *
FROM MY_ORDER
WHERE ORD_PATID = patientId;
END;
The entity definition.
#NamedStoredProcedureQueries(
#NamedStoredProcedureQuery(
name = "fetch_patient_orders",
procedureName = "fetch_patient_orders",
resultClasses = Order.class,
parameters = {
#StoredProcedureParameter(
name = "patientId",
type = Long.class,
mode = ParameterMode.IN
),
#StoredProcedureParameter(
name = "patientOrders",
type = Class.class,
mode = ParameterMode.REF_CURSOR
)
}
)
)
#Entity
#Table(name = "MY_ORDER")
public class Order
{
#Id
#Column(name = "ORD_RECID")
private Long id;
#Column(name = "ORD_CODE")
private String code;
#ManyToOne
#JoinColumn(name = "ORD_PATID")
private Patient patient;
}
And usage:
List<Order> orders = session.createNamedStoredProcedureQuery("fetch_patient_orders")
.setParameter("patientId", 2L)
.getResultList();
It was tested with hibernate 5.4.12.Final, ojdbc8.jar, Oracle12cDialect.
See also the hibernate documentation.
The described above approach will work in a pure hibernate application, but not in spring boot app.
According to the spring boot documentation:
Connection to a Production Database
Production database connections can also be auto-configured by using a pooling DataSource. Spring Boot uses the following algorithm for choosing a specific implementation:
We prefer HikariCP for its performance and concurrency. If HikariCP is available, we always choose it.
Otherwise, if the Tomcat pooling DataSource is available, we use it.
If neither HikariCP nor the Tomcat pooling datasource are available and if Commons DBCP2 is available, we use it.
If you use the spring-boot-starter-jdbc or spring-boot-starter-data-jpa “starters”, you automatically get a dependency to HikariCP.
You can bypass that algorithm completely and specify the connection pool to use by setting the spring.datasource.type property.
So, spring boot uses HikariCP JDBC connection pool by default. And it looks like it has a problem with REF_CURSOR parameter registering:
o.h.r.j.i.ResourceRegistryStandardImpl : Registering statement [HikariProxyCallableStatement#770201936 wrapping oracle.jdbc.driver.OracleCallableStatementWrapper#528a6369]
o.h.type.descriptor.sql.BasicBinder : binding parameter [patientId] as [BIGINT] - [2]
o.h.s.i.AbstractServiceRegistryImpl : Initializing service [role=org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport]
o.h.engine.jdbc.spi.SqlExceptionHelper : Error registering REF_CURSOR parameter [patientOrders] [n/a]
When I use the oracle specific data source pool in the application.properties:
# com.zaxxer.hikari.HikariDataSource (default value)
spring.datasource.type=oracle.jdbc.pool.OracleDataSource
all work fine.

why can't I use "truncate" in spring boot

I wanna use "truncate table" statement instead of "delete" statement in spring boot project cause I need reset the auto increment id in mysql. Here is my code:
#PersistenceContext protected EntityManager em;
#Override
public void removeAllShopeeCategory() {
StringBuilder query = new StringBuilder("truncate table ShopeeCategoryDto shopeecategory");
Query q = this.em.createQuery(query.toString());
q.executeUpdate();
}
but there is an exception like this:
nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: truncate near line 1, column 1
other operation has worked, such as insert, update or select, what's the reason and what should I modify it?
Please use https://docs.oracle.com/javaee/7/api/javax/persistence/EntityManager.html#createNativeQuery-java.lang.String- with native sql queries.
entityManager.createNativeQuery("TRUNCATE TABLE " + tableName + " CASCADE")
.executeUpdate()

sequence does not exist, hibernate and JPA 2.1

I am getting an error saying
`Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
java.sql.SQLSyntaxErrorException: ORA-02289: sequence does not exist`
This error happens when I try to create a user.
#RequestMapping(method = POST)
public UserDto createUser(#RequestBody userDto user) {
Preconditions.checkNotNull(user);
return Preconditions.checkNotNull(service.create(user));
}
I am however able to delete and get just not create nor update. What is also frustrating is I get no error when trying to update, it just doesn't so it.
I am not getting any real lead on where to look. I have tried many different methods to resolve this with no avail.
I found a post that had this:
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQUENCE1")
#SequenceGenerator(name="SEQUENCE1", sequenceName="SEQUENCE1", allocationSize=1)
private int user_id;
At this link: SOF link
It is complaining about this entity which I generated with netbeans and I am currently using Intellij. Any advice would be appreciated.
The code that creates new Campaign entity seems to be incorrect.
public CampaignDto create(CampaignDto campaignDto) {
Campaign campaign = mapper.mapReverse(campaignDto);
System.out.println(campaign.toString());
// Following 2 lines must be added to obtain and use managed Shop entity
Shop existingShop = shopRepository.findOne(campaignDto.getShopId());
campaign.setShop(existingShop);
campaign = campaignRepository.save(campaign);
CampaignDto createdCampaign = mapper.map(campaign);
return createdCampaign;
}
It looks like you might not be setting Campaign.shopId field when creating new Campaign.
#JoinColumn(name = "SHOP_ID", referencedColumnName = "SHOP_ID")
#ManyToOne(optional = false)
private Shop shopId;
You might want to rename this field to just shop to make it clear what it holds as it's not just an identifier.
Depending on how you are persisting new objects you might need to add CascadeType.ALL on #ManyToOne to ensure that a new Shop is persisted together with a new Campaign.
#ManyToOne(optional = false, cascade = CascadeType.ALL)
Go to your application property file and put
hibernate.hbm2ddl.auto=true; It might be helpful Hibernate created this sequence and added a new row

Resources