Hibernate / Spring Data - get executed sql queries count - java

I'm working on presentation in which I would like to show difference in number of executed sql queries between deleteByPost() method with and without custom query. I'm expecting method without custom query to execute 10001 delete queries and with it just 2.
I'm aware of Hibernate Statistics object and it's methods. I was expecting one of them, named getQueryExecutionCount(), to return the number of sql queries executed against db, but what I'm getting is always a 0.
If anyone wonders hibernate statistics are enabled for sure because I'm getting correct numbers on other properties like the count of deleted entities.
Below there is a complete example showing what I am trying to accomplish.
Is there a way to get the number of generated and executed queries using Statistics or any other mechanism? Currently I'm looking at logs (hibernate.show_sql) and counting printed queries but it just seems wrong to me.
package example5
import org.hibernate.SessionFactory
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.data.repository.PagingAndSortingRepository
import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import org.springframework.test.context.junit.jupiter.SpringJUnitJupiterConfig
import org.springframework.transaction.annotation.EnableTransactionManagement
import org.springframework.transaction.annotation.Transactional
import javax.persistence.*
// ENTITIES
#Entity
#Table(name = "posts")
class Post(
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
var id: Long? = null,
#Version
#Column(name = "version")
var version: Long? = null
)
#Entity
#Table(name = "comments")
class Comment(
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
var id: Long? = null,
#Version
#Column(name = "version")
var version: Long? = null,
#JoinColumn(name = "post_id")
#ManyToOne(fetch = FetchType.LAZY)
var post: Post? = null
)
// REPOSITORIES
#Repository
interface PostRepository : PagingAndSortingRepository<Post, Long>
#Repository
interface CommentRepository : PagingAndSortingRepository<Comment, Long> {
#Modifying
#Query("delete from Comment c where c.post = :post")
fun deleteByPost(#Param("post") post: Post)
}
// SERVICES
interface PostService {
fun delete(post: Post)
}
#Service
open class PostServiceImpl(
#Autowired
val postRepository: PostRepository,
#Autowired
val commentRepository: CommentRepository
) : PostService {
#Transactional
override fun delete(post: Post) {
commentRepository.deleteByPost(post)
postRepository.delete(post)
}
}
// CONFIGURATION
#EnableJpaRepositories(basePackages = ["example5"])
#EnableTransactionManagement
#SpringBootApplication(scanBasePackages = ["example5"])
open class FrameworkApplication
// TESTS
#SpringJUnitJupiterConfig(classes = [FrameworkApplication::class])
class Example5(
#Autowired
val postService: PostService,
#Autowired
val postRepository: PostRepository,
#Autowired
val commentRepository: CommentRepository,
#Autowired
val emFactory: EntityManagerFactory
) {
#AfterEach
fun cleanUp() {
commentRepository.deleteAll()
postRepository.deleteAll()
}
#Test
fun testDelete() {
//given
var post = Post()
post = postRepository.save(post)
val comments = mutableListOf<Comment>()
for (i in 1..10000) {
val comment = Comment()
comment.post = post
comments.add(comment)
}
commentRepository.save(comments)
val sessionFactory = emFactory.unwrap(SessionFactory::class.java)
val statistics = sessionFactory.statistics
//then
statistics.clear()
postService.delete(post)
val executedQueryCount = statistics.queryExecutionCount
//then
assertAll(
{ assertEquals(0, postRepository.count()) },
{ assertEquals(0, commentRepository.count()) },
{ assertEquals(2, executedQueryCount) }
)
}
}

The library Spring Hibernate Query Utils (https://github.com/yannbriancon/spring-hibernate-query-utils) provides a query counter that you can use to check the number of queries generated.
If you prefer to do it yourself, Hibernate provides a class EmptyInterceptor that contains a hook named onPrepareStatement.
You can extend this class and add logic in the onPrepareStatement hook to count the queries.
Take a look at the library code to see how to configure the iterator.

The method onPrepareStatement is now deprecated and is removed in the new Hibernate 6 version. The new way to inspect SQL is to implement a StatementInspector .
I've written a little library (https://github.com/Lemick/hibernate-query-asserts) that can assert the count of SQL queries by type (SELECT, INSERT, ..) generated by Hibernate in your Spring tests, this way, you can be warned whenever the SQL statements change in your tests, and prevent N+1 selects. You can take a look here at the project if you want to know how this is implemented.
A test example that demonstrates the purpose:
#Test
#Transactional
#AssertHibernateSQLCount(inserts = 3)
void create_two_blog_posts() {
BlogPost post_1 = new BlogPost("Blog post 1");
post_1.addComment(new PostComment("Good article"));
post_1.addComment(new PostComment("Very interesting"));
blogPostRepository.save(post_1);
}

Related

Using a Hibernate filter with Spring Boot JPA

I have found the need to limit the size of a child collection by a property in the child class.
I have the following after following this guide:
#FilterDef(name="dateFilter", parameters=#ParamDef( name="fromDate", type="date" ) )
public class SystemNode implements Serializable {
#Getter
#Setter
#Builder.Default
// "startTime" is a property in HealthHistory
#Filter(name = "dateFilter", condition = "startTime >= :fromDate")
#OneToMany(mappedBy = "system", targetEntity = HealthHistory.class, fetch = FetchType.LAZY)
private Set<HealthHistory> healthHistory = new HashSet<HealthHistory>();
public void addHealthHistory(HealthHistory health) {
this.healthHistory.add(health);
health.setSystem(this);
}
}
However, I don't really understand how to toggle this filter when using Spring Data JPA. I am fetching my parent entity like this:
public SystemNode getSystem(UUID uuid) {
return systemRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Could not find system with id " + uuid));
}
And this method in turn calls the Spring supported repository interface:
public interface SystemRepository extends CrudRepository<SystemNode, UUID> {
Optional<SystemNode> findByUuid(UUID uuid);
}
How can I make this filter play nicely together with Spring? I would like to activate it programatically when I need it, not globally. There are scenarios where it would be viable to disregard the filter.
I am using Spring Boot 1.3.5.RELEASE, I cannot update this at the moment.
Update and solution
I tried the following as suggested to me in the comments above.
#Autowired
private EntityManager entityManager;
public SystemNode getSystemWithHistoryFrom(UUID uuid) {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("dateFilter");
filter.setParameter("fromDate", new DateTime().minusHours(4).toDate());
SystemNode systemNode = systemRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Could not find system with id " + uuid));
session.disableFilter("dateFilter");
return systemNode;
}
I also had the wrong type in the FilterDef annotation:
#FilterDef(name="dateFilter", parameters=#ParamDef( name="fromDate", type="timestamp" ) )
I changed from date to timestamp.
This returns the correct number of objects, verified against the database.
Thank you!

Spring JPA REST sort by nested property

I have entity Market and Event. Market entity has a column:
#ManyToOne(fetch = FetchType.EAGER)
private Event event;
Next I have a repository:
public interface MarketRepository extends PagingAndSortingRepository<Market, Long> {
}
and a projection:
#Projection(name="expanded", types={Market.class})
public interface ExpandedMarket {
public String getName();
public Event getEvent();
}
using REST query /api/markets?projection=expanded&sort=name,asc I get successfully the list of markets with nested event properties ordered by market's name:
{
"_embedded" : {
"markets" : [ {
"name" : "Match Odds",
"event" : {
"id" : 1,
"name" : "Watford vs Crystal Palace"
},
...
}, {
"name" : "Match Odds",
"event" : {
"id" : 2,
"name" : "Arsenal vs West Brom",
},
...
},
...
}
}
But what I need is to get list of markets ordered by event's name, I tried the query /api/markets?projection=expanded&sort=event.name,asc but it didn't work. What should I do to make it work?
Based on the Spring Data JPA documentation 4.4.3. Property Expressions
... you can use _ inside your method name to manually define traversal points...
You can put the underscore in your REST query as follows:
/api/markets?projection=expanded&sort=event_name,asc
Just downgrade spring.data.‌​rest.webmvc to Hopper release
<spring.data.jpa.version>1.10.10.RELEASE</spring.data.jpa.ve‌​rsion>
<spring.data.‌​rest.webmvc.version>‌​2.5.10.RELEASE</spri‌​ng.data.rest.webmvc.‌​version>
projection=expanded&sort=event.name,asc // works
projection=expanded&sort=event_name,asc // this works too
Thanks #Alan Hay comment on this question
Ordering by nested properties works fine for me in the Hopper release but I did experience the following bug in an RC version of the Ingalls release.bug in an RC version of the Ingalls release. This is reported as being fixed,
jira issue - Sorting by an embedded property no longer works in Ingalls RC1
BTW, I tried v3.0.0.M3 that reported that fixed but not working with me.
We had a case when we wanted to sort by fields which were in linked entity (it was one-to-one relationship). Initially, we used example based on https://stackoverflow.com/a/54517551 to search by linked fields.
So the workaround/hack in our case was to supply custom sort and pageable parameters.
Below is the example:
#org.springframework.data.rest.webmvc.RepositoryRestController
public class FilteringController {
private final EntityRepository repository;
#RequestMapping(value = "/entities",
method = RequestMethod.GET)
public ResponseEntity<?> filter(
Entity entity,
org.springframework.data.domain.Pageable page,
org.springframework.data.web.PagedResourcesAssembler assembler,
org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler entityAssembler,
org.springframework.web.context.request.ServletWebRequest webRequest
) {
Method enclosingMethod = new Object() {}.getClass().getEnclosingMethod();
Sort sort = new org.springframework.data.web.SortHandlerMethodArgumentResolver().resolveArgument(
new org.springframework.core.MethodParameter(enclosingMethod, 0), null, webRequest, null
);
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
Example example = Example.of(entity, matcher);
Page<?> result = this.repository.findAll(example, PageRequest.of(
page.getPageNumber(),
page.getPageSize(),
sort
));
PagedModel search = assembler.toModel(result, entityAssembler);
search.add(linkTo(FilteringController.class)
.slash("entities/search")
.withRel("search"));
return ResponseEntity.ok(search);
}
}
Used version of Spring boot: 2.3.8.RELEASE
We had also the repository for Entity and used projection:
#RepositoryRestResource
public interface JpaEntityRepository extends JpaRepository<Entity, Long> {
}
Your MarketRepository could have a named query like :
public interface MarketRepository exten PagingAndSortingRepository<Market, Long> {
Page<Market> findAllByEventByName(String name, Page pageable);
}
You can get your name param from the url with #RequestParam
This page has an idea that works. The idea is to use a controller on top of the repository, and apply the projection separately.
Here's a piece of code that works (SpringBoot 2.2.4)
import ro.vdinulescu.AssignmentsOverviewProjection;
import ro.vdinulescu.repository.AssignmentRepository;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.PagedModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RepositoryRestController
public class AssignmentController {
#Autowired
private AssignmentRepository assignmentRepository;
#Autowired
private ProjectionFactory projectionFactory;
#Autowired
private PagedResourcesAssembler<AssignmentsOverviewProjection> resourceAssembler;
#GetMapping("/assignments")
public PagedModel<EntityModel<AssignmentsOverviewProjection>> listAssignments(#RequestParam(required = false) String search,
#RequestParam(required = false) String sort,
Pageable pageable) {
// Spring creates the Pageable object correctly for simple properties,
// but for nested properties we need to fix it manually
pageable = fixPageableSort(pageable, sort, Set.of("client.firstName", "client.age"));
Page<Assignment> assignments = assignmentRepository.filter(search, pageable);
Page<AssignmentsOverviewProjection> projectedAssignments = assignments.map(assignment -> projectionFactory.createProjection(
AssignmentsOverviewProjection.class,
assignment));
return resourceAssembler.toModel(projectedAssignments);
}
private Pageable fixPageableSort(Pageable pageable, String sortStr, Set<String> allowedProperties) {
if (!pageable.getSort().equals(Sort.unsorted())) {
return pageable;
}
Sort sort = parseSortString(sortStr, allowedProperties);
if (sort == null) {
return pageable;
}
return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
}
private Sort parseSortString(String sortStr, Set<String> allowedProperties) {
if (StringUtils.isBlank(sortStr)) {
return null;
}
String[] split = sortStr.split(",");
if (split.length == 1) {
if (!allowedProperties.contains(split[0])) {
return null;
}
return Sort.by(split[0]);
} else if (split.length == 2) {
if (!allowedProperties.contains(split[0])) {
return null;
}
return Sort.by(Sort.Direction.fromString(split[1]), split[0]);
} else {
return null;
}
}
}
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At your project incluide #SortByLinkableAssociation at linkable associations that whats sort.
#ManyToOne(fetch = FetchType.EAGER)
#SortByLinkableAssociation
private Event event;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.

Spring Boot Configuration Profiles and #TypeDefs

Fellow SO-er's:
I've been puzzling over this one for a couple of days, and, as of yet, don't have a solution ...
I'm building a Spring Boot web app and what I'd like to be able to do is to activate/deactivate encryption of data fields in my datastore (using the facilities provided by jasypt+spring+hibernate) via activating/deactivating configuration profiles. So that - for development work - I can have data fields stored as clear text, while for production, they would be encrypted.
Currently, I'm doing this via a rather inelegant approach. Specifically, I comment/uncomment code in my package-info.java file where I define two #TypeDefs annotation blocks for the type used for the datastore field- one of which is commented and the other of which is active. Thus, my current package-info.java file is written as follows:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Use this #TypeDefs annotation when dataencrypt configuration profile is active
//#TypeDefs({ #TypeDef(name = com.castlehillgaming.gameshare.model.Ticket.ENCRYPTED_STRING_TYPENAME, typeClass = EncryptedStringType.class, parameters = {
// #Parameter(name = "encryptorRegisteredName", value = com..evilcorp.evilproject.config.EncryptionConfig.REGISTERED_NAME) }) })
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Use this #TypeDefs annotation when dataencrypt configuration profile is not active
#TypeDefs({
#TypeDef(name = com.evilcorp.evilproject.model.Ticket.ENCRYPTED_STRING_TYPENAME, typeClass = String.class, parameters = {
#Parameter(name = "encryptorRegisteredName", value = com..evilcorp.evilproject.config.EncryptionConfig.REGISTERED_NAME) }) })
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
package com..evilcorp.evilproject.model;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.jasypt.hibernate4.type.EncryptedStringType;
And my #Entity Ticket class contains the following:
#Entity
#EqualsAndHashCode(of = { "ticketId" })
#NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class Ticket implements Serializable {
...
#Column(unique = true, nullable = false)
#Type(type = ENCRYPTED_STRING_TYPENAME)
private #Getter String ticketId;
...
}
I'm hoping that I can devise something that will allow me to reduce my package-info.java file to the following:
#TypeDefs({
#TypeDef(name = com.evilcorp.evilproject.model.Ticket.ENCRYPTED_STRING_TYPENAME, typeClass = com.evilcorp.evilproject.config.MyTicketDataFieldString.class, parameters = {
#Parameter(name = "encryptorRegisteredName", value = com..evilcorp.evilproject.config.EncryptionConfig.REGISTERED_NAME) }) })
package com..evilcorp.evilproject.model;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
And define two distinct versions of MyTicketDataFieldString class based on the state of Spring Boot Configuration Profiles. E.g.,
#Configuration
#Profile("dataencrypt")
public class MyTicketDataFieldString extends EncryptedStringType {}
and
#Configuration
#Profile("!dataencrypt")
public class MyTicketDataFieldString implements CharSequence { ... }
where the CharSequence implementation behaves like a vanilla java.lang.String.
But, this won't work because I'll have to define the same class twice in a specific package.
Any ideas on how this can be done (or something equivalent) would be much appreciated.

How to interact with elastic search Alias using Spring data

Hi I am using elastic search Spring data. Domain structure of my project keeps on changing.So I have to drop the index in order to change the mapping every time. To overcome this problem, I am using Aliases.
I created an Alias using:
elasticsearchTemplate.createIndex(Test.class);
elasticsearchTemplate.putMapping(Test.class);
String aliasName = "test-alias";
AliasQuery aliasQuery = new AliasBuilder()
.withIndexName("test")
.withAliasName(aliasName).build();
elasticsearchTemplate.addAlias(aliasQuery);
I have a test class:
import org.springframework.data.annotation.Id
import org.springframework.data.elasticsearch.annotations.Document
import org.springframework.data.elasticsearch.annotations.Field
import org.springframework.data.elasticsearch.annotations.FieldIndex
import org.springframework.data.elasticsearch.annotations.FieldType
import org.springframework.data.elasticsearch.annotations.Setting
#Document(indexName = "test", type = "test")
#Setting(settingPath = 'elasticSearchSettings/analyzer.json')
class Test extends BaseEntity{
#Id
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
String id
#Field(type = FieldType.String, index = FieldIndex.analyzed, indexAnalyzer = "generic_analyzer", searchAnalyzer = "generic_analyzer")
String firstName
}
TestRepository Class:
package com.as.core.repositories
import com.as.core.entities.Test
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository
interface TestRepository extends ElasticsearchRepository<Test, String>
{
}
My question is how can I read from alias instead of the index itself?
Does write operation also takes place on alias.
I have looked at following link:
https://www.elastic.co/guide/en/elasticsearch/guide/current/index-aliases.html#index-aliases
It says that we will have to interact the alias instead of the actual index.How to achieve this using Elasticsearch Spring data Java API.
I have worked around this limitation by using the ElasticsearchTemplate in the repository class associated with the object (although it would be much nicer if there was a way to specify an alias name on the entity itself).
The way it works is to create a custom repository interface. In your case it would be TestRepositoryCustom:
public interface TestRepositoryCustom
{
Test> findByCustom(...);
}
Then implement this interface appending 'Impl' to the end of the base repository name:
public class TestRepositoryImpl implements TestRepositoryCustom
{
Page<Test> findByCustom(Pageable pageable, ...)
{
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
FilterBuilder filter = FilterBuilders.staticMethodsToBuildFilters;
/*
* Your code here to setup your query
*/
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder().withQuery(boolQuery).withFilter(filter).withPageable(pageable);
//These two are the crucial elements that will allow the search to look up based on alias
builder.withIndices("test-alias");
builder.withTypes("test");
//Execute the query
SearchQuery searchQuery = builder.build();
return elasticSearchTemplate.queryForPage(searchQuery, Test.class);
}
}
Finally in your base JPA repsitory interface, TestRepository, extend the TestRepositoryCustom interface to get access to any methods on your custom interface from your repository bean.
public interface TestRepository extends ElasticsearchRepository<Consultant, String>, TestRepositoryCustom
{
}
What I would really like to see is an annotation on the entity like:
#Document(aliasName="test-alias")
This would just work in the background to provide searching on this index out of the gate so that all the jpa queries would just work regardless of the index name.
Spring-data-elasticsearch supports finding documents in an alias. We were able to make version 3.2.6.RELEASE read documents annotated with
#Document(
indexName = "alias",
createIndex = false,
type = '_doc'
)
from a Spring Data ElasticsearchRepository backed by a ElasticsearchRestTemplate

JPA - Increment a numeric field through a sequence programmatically

I have a JPA 2 web application (Struts 2, Hibernate 4 as JPA implementation only).
The current requirement is to add a (non-id) numeric sequential field, filled for certain rows only, to an existing entity. When inserting a new row, based on a certain condition, I need to set the new field to its highest value + 1 or to NULL.
For example:
ID NEW_FIELD DESCRIPTION
--------------------------------
1 1 bla bla
2 bla bla <--- unmatched: not needed here
3 bla bla <--- unmatched: not needed here
4 2 bla bla
5 3 bla bla
6 4 bla bla
7 bla bla <--- unmatched: not needed here
8 5 bla bla
9 bla bla <--- unmatched: not needed here
10 6 bla bla
In the good old SQL, it would be something like:
INSERT INTO myTable (
id,
new_field,
description
) VALUES (
myIdSequence.nextVal,
(CASE myCondition
WHEN true
THEN myNewFieldSequence.nextVal
ELSE NULL
END),
'Lorem Ipsum and so on....'
)
But I've no clue on how to achieve it with JPA 2.
I know I can define callbacks methods, but JSR-000317 Persistence Specification for Eval 2.0 Eval discourages some specific operations from inside it:
3.5 Entity Listeners and Callback Methods
- Lifecycle callbacks can invoke JNDI, JDBC, JMS, and enterprise beans.
- In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity
instances, or modify relationships within the same persistence
context.[43] A lifecycle callback method may modify the
non-relationship state of the entity on which it is invoked.
[43] The semantics of such operations may be standardized
in a future release of this specification.
Summarizing, yes to JDBC (!) and EJB, no to EntityManager and other Entities.
EDIT
I'm trying to achieve the solution described in the answer from #anttix, but I'm encoutering some problem, so please correct me where I'm wrong.
Table
MyTable
-------------------------
ID number (PK)
NEW_FIELD number
DESCRIPTION text
Main Entity
#Entity
#Table(name="MyTable")
public class MyEntity implements Serializable {
#Id
#SequenceGenerator(name="seq_id", sequenceName="seq_id", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_id")
private Long id;
#OneToOne(cascade= CascadeType.PERSIST)
private FooSequence newField;
private String description
/* Getters and Setters */
}
Sub entity
#Entity
public class FooSequence {
#Id
#SequenceGenerator(name="seq_foo", sequenceName="seq_foo", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_foo")
private Long value;
/* Getter and Setter */
}
DAO
myEntity.setNewField(new FooSequence());
entityManager.persist(myEntity);
Exception
Caused by: javax.transaction.RollbackException: ARJUNA016053: Could not commit transaction.
[...]
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: ERROR: relation "new_field" does not exist
[...]
Caused by: org.hibernate.exception.SQLGrammarException: ERROR: relation "new_field" does not exist
[...]
Caused by: org.postgresql.util.PSQLException: ERROR: relation "new_field" does not exist
What am I doing wrong ? I'm pretty new to JPA 2 and I've never used an entity not associated to a physical table... this approach is totally new to me.
I guess I need to put the #Column definition somewhere: how could JPA possibly know that the newField column (mapped through ImprovedNamingStrategy to new_field on the database) is retrieved through the value property of the FooSequence entity ?
Some pieces of the puzzle are missing.
EDIT
As asked in comments, this is the persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="MyService" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/myDS</jta-data-source>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.ImprovedNamingStrategy"/>
<property name="hibernate.query.substitutions"
value="true 'Y', false 'N'"/>
<property name="hibernate.show_sql" value="true" />
<property name="format_sql" value="true" />
<property name="use_sql_comments" value="true" />
</properties>
</persistence-unit>
</persistence>
One possible solution is to use a separate entity with its own table that will encapsulate only the new field and have an OneToOne mapping with that entity. You will then instantiate the new entity only when you encounter an object that needs the additional sequence number. You can then use any generator strategy to populate it.
#Entity
public class FooSequence {
#Id
#GeneratedValue(...)
private Long value;
}
#Entity
public class Whatever {
#OneToOne(...)
private FooSequnce newColumn;
}
See:
Hibernate JPA Sequence (non-Id)
https://forum.hibernate.org/viewtopic.php?p=2405140
A gradle 1.11 runnable SSCCE (using Spring Boot):
src/main/java/JpaMultikeyDemo.java
import java.util.List;
import javax.persistence.*;
import lombok.Data;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
#Configuration
#EnableTransactionManagement
#EnableAutoConfiguration
public class JpaMultikeyDemo {
#Entity #Data
public static class FooSequence {
#Id #GeneratedValue private Long value;
}
#Entity #Data
public static class FooEntity {
#Id #GeneratedValue private Long id;
#OneToOne private FooSequence sequence;
}
#PersistenceContext
EntityManager em;
#Transactional
public void runInserts() {
// Create ten objects, half with a sequence value
for(int i = 0; i < 10; i++) {
FooEntity e1 = new FooEntity();
if(i % 2 == 0) {
FooSequence s1 = new FooSequence();
em.persist(s1);
e1.setSequence(s1);
}
em.persist(e1);
}
}
public void showAll() {
String q = "SELECT e FROM JpaMultikeyDemo$FooEntity e";
for(FooEntity e: em.createQuery(q, FooEntity.class).getResultList())
System.out.println(e);
}
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(JpaMultikeyDemo.class);
context.getBean(JpaMultikeyDemo.class).runInserts();
context.getBean(JpaMultikeyDemo.class).showAll();
context.close();
}
}
build.gradle
apply plugin: 'java'
defaultTasks 'execute'
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-milestone" }
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-data-jpa:1.0.0.RC5"
compile "org.projectlombok:lombok:1.12.6"
compile "com.h2database:h2:1.3.175"
}
task execute(type:JavaExec) {
main = "JpaMultikeyDemo"
classpath = sourceSets.main.runtimeClasspath
}
See also: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-configure-datasource
This looks like it could be a good case for some AOP. First start by creating a custom field annotation #CustomSequenceGeneratedValue, and then annotate the field on the entity with it:
public class MyEntity {
...
#CustomSequenceGeneratedValue
private Long generatedValue;
public void setGeneratedValue(long generatedValue) {
}
}
Then an aspect is created to increment generated values:
#Aspect
public class CustomSequenceGeneratedValueAspect {
#PersistenceContext
private EntityManager em;
#Before("execution(* com.yourpackage.dao.SomeDao.*.*(..))")
public void beforeSaving(JoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
MethodSignature ms = (MethodSignature) jp.getSignature();
Method m = ms.getMethod();
Annotation[][] parameterAnnotations = m.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
for (Annotation annotation : annotations) {
if (annotation.annotationType() == CustomSequenceGeneratedEntity.class) {
... find generated properties run query and call setter ...
... Query query = em.createNativeQuery("select MY_SEQUENCE.NEXTVAL from dual");
}
}
}
}
}
Then the aspect is scanned with <aop:aspectj-autoproxy />, and applied to any Spring DAO saving entities of this type. The aspect would populate the sequence generated values based on a sequence, in a transparent way for the user.
You mentioned being open to using JDBC. Here is how you can you use Entity Callback with JdbcTemplate, the example uses Postgres's syntax for selecting next value in a sequence, just update it to use the right syntax for your DB.
Add this to your entity class:
#javax.persistence.EntityListeners(com.example.MyEntityListener.class)
And here is listener implementation (#Qualifier and required = true are necessary for it to work):
package com.example;
import javax.persistence.PostPersist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
#Component
public class MyEntityListener {
private static JdbcTemplate jdbcTemplate;
#Autowired(required = true)
#Qualifier("jdbcTemplate")
public void setJdbcTemplate(JdbcTemplate bean) {
jdbcTemplate = bean;
}
#PostPersist
#Transactional
public void postPersis(MyEntity entity) {
if(isUpdateNeeded(entity)) {
entity.setMyField(jdbcTemplate.queryForObject("select nextval('not_hibernate_sequence')", Long.class));
}
}
private boolean isUpdateNeeded(MyEntity entity) {
// TODO - implement logic to determine whether to do an update
return false;
}
}
The hacky solution I used to keep it simple is the following:
MyEntity myEntity = new MyEntity();
myEntity.setDescription("blabla");
em.persist(myEntity);
em.flush(myEntity);
myEntity.setNewField(getFooSequence());
The complete code ("pseudo-code", I've written it directly on SO so it could have typos) with transaction handling would be like :
Entity
#Entity
#Table(name="MyTable")
public class MyEntity implements Serializable {
#Id
#SequenceGenerator(name="seq_id", sequenceName="seq_id", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq_id")
private Long id;
private Long newField; // the optional sequence
private String description
/* Getters and Setters */
}
Main EJB:
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER) // default
public class MainEjb implements MainEjbLocalInterface {
#Inject
DaoEjbLocalInterface dao;
// Create new session, no OSIV here
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Long insertMyEntity(boolean myCondition) throws Exception {
try {
MyEntity myEntity = dao.insertMyEntity();
// if this break, no FooSequence will be generated
doOtherStuff();
// Do other non-database stuff that can break here.
// If they break, no FooSequence will be generated,
// and no myEntity will be persisted.
if (myCondition) {
myEntity.setNewField(dao.getFooSequence());
// This can't break (it would have break before).
// But even if it breaks, no FooSequence will be generated,
// and no myEntity will be persisted.
}
} catch (Exception e){
getContext().setRollbackOnly();
log.error(e.getMessage(),e);
throw new MyException(e);
}
}
}
DAO EJB
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER) // default
public class DaoEjb implements DaoEjbLocalInterface {
#PersistenceContext( unitName="myPersistenceUnit")
EntityManager em;
// default, use caller (MainEJB) session
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public MyEntity insertMyEntity() throws Exception{
MyEntity myEntity = new MyEntity();
myEntity.setDescription("blabla");
em.persist(myEntity);
em.flush(); // here it will break in case of database errors,
// eg. description value too long for the column.
// Not yet committed, but already "tested".
return myEntity;
}
// default, use caller (MainEJB) session
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public Long getFooSequence() throws Exception {
Query query = em.createNativeQuery("SELECT nextval('seq_foo')");
return ((BigInteger) query.getResultList().get(0)).longValue();
}
}
This will guarantee there will be no gaps in the FooSequence generation.
The only drawback, that I don't care at all in my use case, is that FooSequence and the #Id sequence are not synchronized, so two concurrent inserts may have "inverted" FooSequence values, respecto to their order of arrive, eg.
ID NEW FIELD
-------------
1 2
2 1

Categories

Resources