I want to use custom sequence(orderId_seq) while saving my entity instead of default one(hibernate_sequence) in postgres-9.5.
My entity is :
#Entity
#Table(name="orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "orderId_generator")
#SequenceGenerator(name="orderId_generator", sequenceName = "orderId_seq")
#Column(updatable = false, nullable = false)
private Long orderId;
#Column(columnDefinition = "uuid",nullable = false)
#Type(type="pg-uuid")
private UUID userId;
private String comments;
private String assignedTo;
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private OrderStatus status;
#Column(nullable = false, updatable = false, insertable = false,
columnDefinition = "TIMESTAMP with time zone DEFAULT CURRENT_TIMESTAMP")
private Timestamp orderedDate;
//getters and setters
when I save this entity to the database. It is generating some random number for orderId instead of using orderId_seq.
When I check the order entity in debug mode before saving it is null but after saving it is creating some random number not even using hibernate_sequence.
And when I use show_sql=true option in hibernate it is even showing the following statement in the console :
Hibernate:
select
nextval ('orderId_seq')
How can I resolve this issue?
Related
Hi hibernate (envers) experts!
I currently have two/three entities
Automations which represent an automated process for one answer of a question (of a survey)
Answers answer of a user for a question (of a survey)
BaseEntity which is extended by Automations and Answers an has basic attributes like modify timestamps and so on.
The Automations table is audited by hibernate envers.
I want to fetch all deleted Automations for one specific Answer. The generated and executed query from hibernate does include my condition for the "deleted" revtype of envers but not my condition for the answer.
See the function listHistoryByAnswer
#MappedSuperclass
public class BaseEntity extends PanacheEntityBase {
#Id
#Column(name = "UUID", updatable = false, nullable = false, columnDefinition = "uuid")
private UUID _uuid;
#Column(name = "CREATED_AT", nullable = false, updatable = false, columnDefinition = "TIMESTAMP without time zone")
#CreationTimestamp
private Timestamp createdAt;
#Column(name = "CREATED_BY", nullable = false, updatable = false)
private String createdBy;
#Column(name = "UPDATED_AT", columnDefinition = "TIMESTAMP without time zone")
#UpdateTimestamp
private Timestamp updatedAt;
#Column(name = "UPDATED_BY")
private String updatedBy;
}
#Entity
#Table(name = "AUTOMATION", uniqueConstraints = #UniqueConstraint(columnNames = {"ANSWER_UUID"}))
#Audited
public class Automation extends BaseEntity {
#Basic
#Column(name = "DESCRIPTION")
private String description;
#JsonBackReference("automation-answer")
#ManyToOne
#JoinColumn(name = "ANSWER_UUID", nullable = false)
private Answer answer;
}
#Entity
#Table(name = "ANSWER")
#Audited(targetAuditMode = NOT_AUDITED)
public class Answer extends BaseEntity {
#JsonManagedReference("automation-answer")
#OneToMany(mappedBy = "answer", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Automation> automations = new ArrayList<>();
#Column(name = "DATA", columnDefinition = "jsonb")
private JsonNode data;
}
/**
* List old (deleted) questions based on the answer
* #param answerUuid unique id of the answer
* #return list with deleted questions
*/
public List<Automation> listHistoryByAnswer(final UUID answerUuid){
List resultList = AuditReaderFactory.get(getEntityManager())
.createQuery()
.forRevisionsOfEntity(Automation.class, true)
.add(AuditEntity.revisionType().eq(RevisionType.DEL))
.add(AuditEntity.property("answer_uuid").eq(answerUuid))
.getResultList();
return resultList;
}
Generated SQL
select automation0_.UUID as uuid1_1_0_,
automation0_.REV as rev2_1_0_,
defaultrev1_.REV as rev1_15_1_,
automation0_.REVTYPE as revtype3_1_0_,
automation0_.ANSWER_UUID as answer_u9_1_0_,
defaultrev1_.REVTSTMP as revtstmp2_15_1_
from A_AUTOMATION_HIS automation0_
cross join
REVINFO defaultrev1_
where automation0_.REVTYPE = 2 -- DELETED entities
and automation0_.REV = defaultrev1_.REV
order by automation0_.REV asc;
Given table schema as below (this is mysql syntax, but does not matter)
-- base table keeping all subscription data
CREATE TABLE user_subscription (
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id INTEGER NOT NULL,
data VARCHAR(2000) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- supportive view focusing on every user's latest row
CREATE VIEW user_subscription_latest AS
SELECT s1.*
FROM user_subscription s1
LEFT JOIN user_subscription s2
ON s2.user_id = s1.user_id AND s2.updated_at > s1.updated_at
WHERE s2.id IS NULL;
And I have following entity class for base table user_subscription
import javax.persistence.*;
#Entity
#Table(name = "user_subscription")
public class UserSubscription implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false, insertable = false)
private Integer id;
#Column(name = "user_id", nullable = false)
private Integer userId;
#Column(name = "data", nullable = true, length = 2000)
private String data;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at", nullable = false, updatable = false, insertable = false)
private java.time.LocalDateTime createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_at", nullable = false, updatable = false, insertable = false)
private java.time.LocalDateTime updatedAt;
}
And following Spring Data JPA repository (still does not matter. it can be any JPA scenario)
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface UserSubscriptionRepository extends JpaRepository<UserSubscription, Integer> {
// TODO How can I achieve this without using nativeQuery?
#Query(value = "SELECT * FROM user_subscription_latest", nativeQuery = true)
java.util.List<UserSubscription> findLatest();
}
Since view user_subscription_latest is subset of user_subscription, they are interchangeable, but I have no idea how to put them together.
My question is what is the correct/preferred way to design the JPA entity so that I can take advantage of supportive view user_subscription_latest on query while keeping accessibility to base table user_subscription, without nativeQuery?
To answer your question on what is the recommended way (other than using a nativeQuery) to gain accessibility to your view as well as maintain your existing accessibility to the user table, the best possibility is to create another entity something like the one below,
import javax.persistence.*;
#Entity
#Immutable
public class UserSubscriptionLatest implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false, insertable = false)
private Integer id;
#Column(name = "user_id", nullable = false)
private Integer userId;
#Column(name = "data", nullable = true, length = 2000)
private String data;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at", nullable = false, updatable = false, insertable = false)
private java.time.LocalDateTime createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_at", nullable = false, updatable = false, insertable = false)
private java.time.LocalDateTime updatedAt;
}
Making your entity class immutable is the most important step to working with database views in Spring Data JPA. Based on this mapping, Hibernate will ensure that your application doesn’t perform any write operations on the view. To make your persistence layer easier to use, you should take this one step further and also exclude all methods from your repository that persist, update or remove an entity.
Then create a readOnlyRepository defining an interface that extends the Repository interface and copying a few method definitions from Spring Data JPA’s standard repositories like the one below
#NoRepositoryBean
public interface ReadOnlyRepository<T, ID> extends Repository<T, ID> {
List<T> findAll();
List<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
Optional<T> findById(ID id);
long count();
}
Then create your JPArepository by extending the readOnlyRepository that we just created, this ensures that your repository is accessible for readOnly purposes and also tells the hibernate to eschew any checks pertaining to write/update
public interface UserSubscriptionLatestRepository extends ReadOnlyRepository<UserSubscriptionLatest, Integer> {
List<UserSubscriptionLatest> findById();
}
Hope I had answered your question
let's consider two JPA entities A and B :
#Entity
#Table(name = "A")
#SequenceGenerator(name = "seq_a", allocationSize = 50, initialValue = 1)
public class A {
#Id
#GeneratedValue(generator = "seq_a", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
private Long id;
#Column(name = "CODE")
private String code;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<B> bSet = new HashSet<>();
#Column(name = "CREATED_TIME")
private LocalDateTime createdTime;
//getters + setters
}
#Entity
#Table(name = "B")
#SequenceGenerator(name = "seq_b", allocationSize = 50, initialValue = 1)
public class B {
#Id
#GeneratedValue(generator = "seq_b", strategy = GenerationType.SEQUENCE)
#Column(name = "ID", insertable = false, updatable = false, unique = true, nullable = false)
private Long id;
#Column(name = "SOMETHING")
private String something;
#ManyToOne(optional = false)
#JoinColumn(name = "A_ID", nullable = false, updatable = false)
private A a;
#Column(name = "CREATED_TIME")
private LocalDateTime createdTime;
//getters + setters
}
then consider RestController (springboot context) that have one GET method used for retrieving detail of entity A :
#GetMapping("/{id}")
public ResponseEntity<ADTO> getA(#PathVariable(name = "id", required = true) Long id) {
return aRepository.findById(id)
.map(a -> new ResponseEntity<>(mapper.mapToDomain(a), HttpStatus.OK))
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
method POST used for creating records of A:
#PostMapping
public ResponseEntity<ADTO> addA(#RequestBody #Valid ADTO aDTO) {
return new ResponseEntity<>(mapper.mapToDomain(a.save(mapper.mapToEntity(ADTO))), HttpStatus.CREATED);
}
and PUT method for updating :
#PutMapping
public ResponseEntity<ADTO> updateA(#RequestBody #Valid ADTO aDTO) {
A a = aRepository.findById(aDTO.getId()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
ADTO aDTOfound = mapper.mapToDomain(a);
BeanUtils.copyProperties(aDTO, aDTOfound);
return new ResponseEntity<>(mapper.mapToDomain(aRepository.save(mapper.mapToEntity(aDTOfound), HttpStatus.OK)));
}
then let's say that, createdTime attribute is updated everytime the entity is persisted (including created - updating createdTime attribute is done under the hood ...).
Then let's consider scenario, where two users at the same time are retrieving detail of the same entity A (id 1). If user X update the detail from the retrieved content via PUT method, is there any way how to avoid user Y to update the same entity with old content (notice that the createdTime attribute is updated on record with id 1) ? I know that one possible solution, is to make sure that the retrieved createdTime and one from aDTO in update method is the same, but is there any "more" standard solution for this problem ? For example, how to avoid updating entity A (if it was updated previously with USER 1) but let update the childs in Bset which ones for example were not updated by user 1 ...
This is typical problem statement of Optimistic Locking
Optimistic locking is a mechanism that prevents an application from
being affected by the "lost update" phenomenon in a concurrent
environment while allowing some high degree of concurrency at the same
time.
I will solve this problem using #Version, add #Version field in your entity like below
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name = "student_name")
private String studentName;
#Column(name = "roll_number")
private String rollNumber;
#Column(name = "version")
#Version
private Long version;
}
In above case When we create an entity for the first time default version value will be zero
On update, the field annotated with #Version will be incremented and subsequent update will same version will fail with OptimisticLockException
The #Version annotation in hibernate is used for Optimistic locking while performing update operation. For more details you can visit https://vladmihalcea.com/jpa-entity-version-property-hibernate/
I have simple class:
#Entity
#Table(name = "task")
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private int id;
#Column(name = "date_time", unique = false, nullable = false)
private LocalDateTime date_time;
}
In my import.sql file, I have following:
INSERT INTO todo_project.task ( active, date_time, title, done) VALUES ( true, 'some-string-for-date-time', 'do homework', false);
What to set for some-string-for-date-time?
I need to recreate this query:
select distinct
limits.domains_limit,
limits.emails_limit,
limits.keywords_limit
from user_schema.tenant_limits as limits
join user_schema.tenant as tenants on limits.tenant_id = tenants.id
join profile_schema.profile as profiles on tenants."name" = 'test'
The relevant part of the tables I have to go through are:
PROFILE(Table)
- Tenant (refer to TENANTS.name)
TENANTS (Table)
- id
- name
LIMITS (Table)
- tenant_id (refer to TENANTS.id)
And what I basically need to do is given a Profile (which contains a tenant name) I need to fetch the limits for the tenant. How can I set it up with Hibernate Annotations?
Profile.java
public class Profile {
// other properties ...
#Column(name = "tenant", nullable = false, updatable = false)
private String tenant;
//How to fetch limits here?
private ProfileLimits limits;
}
TenantsLimit
public class ProfileLimits {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long tenant_id;
#Column(name = "domains_limit", nullable = true, updatable = true)
private Long domains;
#Column(name = "emails_limit", nullable = true, updatable = true)
private Long emails;
#Column(name = "keywords_limit", nullable = true, updatable = true)
private Long keywords;
}