Spring Data JPA - map array of polygons - spring

I define this column in postgresql
ALTER TABLE "my_table" ADD COLUMN polygons geometry[] default '{}';
then in the model i try to map it
#Column(name = "polygons", columnDefinition = "polygon[]")
private List<Polygon> polygons = new ArrayList<>();
But i get
Could not determine type for: java.util.List, at table: my_table, for columns: [org.hibernate.mapping.Column(polygons)]
I've tried but none of them worked
#Column(name = "polygons", columnDefinition = "polygon[]") // 1
#Column(name = "polygons", columnDefinition = "geometry[]") // 2
#Type(type = "org.locationtech.jts.geom.Polygon[]") // 3
private List<Polygon> polygons = new ArrayList<>();

Related

Manage the update order for queries in JPA

I am creating a simple kanban application as following, each kanban is made out of a sequence of stages and each stage have a level field to define its position. I want to be able to add, move and remove stages at will so I have to keep the level of each stage consistent, simple enough.
#Entity
#Table(name = "kanbans")
data class Kanban (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
var id: Int? = null,
#get:NotNull
#get:NotBlank
#Column(name = "name", nullable = false)
var name: String? = null,
#get:NotNull
#get:NotBlank
#Column(name = "description", nullable = false)
var description: String? = null,
#get:NotNull
#Column(name = "closed", nullable = false)
var closed: Boolean? = null,
#get:NotNull
#Column(name = "created_at", nullable = false)
var createdAt: LocalDateTime? = null,
#get:NotNull
#Column(name = "updated_at", nullable = false)
var updatedAt: LocalDateTime? = null,
)
#Entity
#Table(name = "stages")
data class Stage (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
var id: Int? = null,
#get:NotNull
#get:NotBlank
#Column(name = "name", nullable = false)
var name: String? = null,
#get:NotNull
#get:NotBlank
#Column(name = "description", nullable = false)
var description: String? = null,
#get:NotNull
#Column(name = "closed", nullable = false)
var closed: Boolean? = null,
#get:NotNull
#Column(name = "level", nullable = false)
var level: Int? = null,
#OneToMany(fetch = FetchType.LAZY, mappedBy = "stage")
var tasks: List<Task> = ArrayList(),
#get:NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "kanban_id", nullable = false)
var kanban: Kanban? = null,
#get:NotNull
#Column(name = "created_at", nullable = false)
var createdAt: LocalDateTime? = null,
#get:NotNull
#Column(name = "updated_at", nullable = false)
var updatedAt: LocalDateTime? = null,
)
When creating the first stage its always assigning its level at 0 and then when adding new ones the level will define the stage position at the list of stages. The problem is that when I try to update the previous existing stages to give place to the new one, the only way I found to make this work is to place a saveAndFlush call in a loop but I find it to be not a good ideia.
#Repository
interface StageRepository : JpaRepository<Stage, Int> {
fun findAllByKanbanAndLevelGreaterThanEqualOrderByLevelDesc(kanban: Kanban, level: Int): List<Stage>
#Modifying
#Transactional
#Query("UPDATE Stage s SET s.level = s.level + 1 WHERE s.kanban = :kanban AND s.level >= :level")
fun incrementLevelForKanbanStagesWhereLevelIsGreaterThan(kanban: Kanban, level: Int)
}
the incrementLevelForKanbanStagesWhereLevelIsGreaterThan method fails as the database have a unique constraint to level and kanban_id with the following error:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "stages_kanban_id_level_key"
Detalhe: Key (kanban_id, level)=(337, 1) already exists.
this is obviously happening because it is trying to update level 0 to level 1 before updating level 1 to level 2 and so I have tried:
#Transactional
#Query("UPDATE Stage s SET s.level = s.level + 1 WHERE s.kanban = :kanban AND s.level >= :level ORDER BY s.level DESC")
fun incrementLevelForKanbanStagesWhereLevelIsGreaterThan(kanban: Kanban, level: Int)
which does not compile,
#Service
#Transactional
class StageCrudService: CrudService<Stage, Int, StageRepository, StageValidationService>() {
#Throws(ValidationException::class)
override fun create(model: Stage): Stage {
prepareToCreate(model)
validationService.canSave(model)
incrementKanbanStageLevels(model)
return repository.save(model)
}
private fun prepareToCreate(model: Stage) {
val now = LocalDateTime.now()
val closed = model.closed ?: false
model.closed = closed
model.createdAt = now
model.updatedAt = now
model.level = model.level ?: 0
}
private fun incrementKanbanStageLevels(model: Stage) {
val level = model.level ?: 0
val stages = repository.findAllByKanbanAndLevelGreaterThanEqualOrderByLevelDesc(model.kanban!!, level)
stages.forEach { stage ->
stage.level = stage.level?.plus(1)
}
repository.saveAll(stages)
repository.flush()
}
}
and
private fun incrementKanbanStageLevels(model: Stage) {
val level = model.level ?: 0
val stages = repository.findAllByKanbanAndLevelGreaterThanEqualOrderByLevelDesc(model.kanban!!, level)
stages.forEach { stage ->
stage.level = stage.level?.plus(1)
repository.save(stage)
}
repository.flush()
}
but both fails the same way as the query. Now the question is:
Is there a better way to manage the update order for this kind of situation instead of doing:
private fun incrementKanbanStageLevels(model: Stage) {
val level = model.level ?: 0
val stages = repository.findAllByKanbanAndLevelGreaterThanEqualOrderByLevelDesc(model.kanban!!, level)
stages.forEach { stage ->
stage.level = stage.level?.plus(1)
repository.saveAndFlush(stage)
}
}
It seems to me that you are possibly trying to implement something that can be managed for you via the JPA #OrderColumn annotation:
https://docs.oracle.com/javaee/7/api/javax/persistence/OrderColumn.html
Specifies a column that is used to maintain the persistent order of a
list. The persistence provider is responsible for maintaining the
order upon retrieval and in the database. The persistence provider is
responsible for updating the ordering upon flushing to the database to
reflect any insertion, deletion, or reordering affecting the list.
To use this you would need to make the relationship bi-directional and the level should be maintained by your JPA provider as items are added to and removed from the list
#Entity
#Table(name = "kanbans")
data class Kanban (
.....
#get:NotNull
#get:NotBlank
#OrderColumn(name = "level")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "kanban")
var stage: List<Stage> = ArrayList()
.....
}
So you can then remove and add items (at any position) and the sequence will be maintained for you.

Criteria API Specification - Filter records and return only the latest record for many to one mappings

I have two tables, Lead and LeadActivity. A lead can have many lead activities and mapping is done as #ManyToOne form LeadActivity to Lead.
Problem Statement -I want to to filter LeadActivity records such that, If there are more than one leadactivity records with same leadId, i should get only one record which is latest (have max primary key). Can anyone guide me on how to write specification or criteria API in such situations? I know this can be achieved through other ways but I have to use specification API. Below are the entity classes
Lead
#Entity
#Table(name = "customer_lead")
#Where(clause = ReusableFields.SOFT_DELETED_CLAUSE)
#Audited(withModifiedFlag = true)
#Data
public class Lead extends ReusableFields implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "lead_id", updatable = false, nullable = false)
Long leadId;
#Column(name = "name")
String customerName;
#Column(name = "primary_mobile")
String primaryMobile;
#Column(name = "secondary_mobile")
String secondaryMobile;
//more fields
}
Lead Activity
#Entity
#Table(name = "LeadActivity")
#Data
#Where(clause = ReusableFields.SOFT_DELETED_CLAUSE)
public class LeadActivity extends ReusableFields implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "leadactivity_id", updatable = false, nullable = false)
Long leadActivityId;
#Column(name = "activity_date_time", nullable = false)
#NonNull
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm")
Date activityDateTime;
#Column(name = "title")
#NonNull
String title;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "lead_id", nullable = false)
#JsonIgnoreProperties(
{ "hibernateLazyInitializer", "handler" })
#NotFound(action = NotFoundAction.IGNORE)
Lead lead;
//More fields
}
Expected Output - Suppose there are two records present with same leadId. I want to fetch only the latest among them based on their id. One with lower id should be ignored
Try this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<LeadActivity> cq = cb.createQuery(LeadActivity.class);
Root<LeadActivity> rootLeadActivity = cq.from(LeadActivity.class);
Join<LeadActivity,Lead> joinLead = rootLeadActivity.join(LeadActivity_.lead,JoinType.INNER);
/* If you dont use metamodel:
* Join<LeadActivity,Lead> joinLead = rootLeadActivity.join("lead",JoinType.INNER);
*/
// For performance, if you use JPA 2.1 set the leader id condition in the join
joinLead.on(cb.equal(joinLead.get(Lead_.leadId),LEAD_ID));
List<Predicate> predicatesList= new ArrayList<>();
/* if you use version 2.0 you will have to put it in the where
* predicatesList.add(cb.equal(joinLead.get(Lead_.leadId),LEAD_ID));
*/
Subquery<Long> sqMaxId = cq.subquery(Long.class);
Root<LeadActivity> sqRootActivity = sqMaxId.from(LeadActivity.class);
Join<LeadActivity,Lead> sqJoinLead = sqRootActivity.join(LeadActivity_.lead,JoinType.INNER);
sqMaxId.where(cb.equal(sqJoinLead.get(Lead_.leadId),joinLead.get(Lead_.leadId)));
sqMaxId.select(cb.max(sqRootActivity.get(LeadActivity_.leadActivityId)));
predicatesList.add(cb.equal(rootLeadActivity.get(LeadActivity_.leadActivityId),sqMaxId.getSelection()));
cq.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
cq.multiselect(rootLeadActivity);
The result query:
select a.* from lead_activity a
inner join lead l on a.lead_id = l.lead_id and l.lead_id = LEAD_ID
where a.lead_activity_id =
(select max(lead_activity_id) from lead_activity where lead_id = LEAD_ID)

Spring Data JPA update a Row without getting the row ById

I want to update the table using spring-jpa
This is my Entity Class
public class RewardEntity {
#Id
#Column(name = "reward_id", columnDefinition = "bigserial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long rewardId;
#Column(name = "reward_title", nullable = false)
private String rewardTitle;
#Column(name = "reward_text")
private String rewardText;
#Column(name = "reward_type", nullable = false)
private String rewardType;
#Column(name = "reward_for_code", nullable = false)
private String rewardFor;
#Column(name = "reward_from_date", nullable = false)
private OffsetDateTime rewardFromDate;
#Column(name = "reward_to_date", nullable = false)
private OffsetDateTime rewardToDate;
#Column(name = "is_display_on", nullable = false)
private Boolean isDisplayOn;
#Column(name = "created_id", length = 50, nullable = false)
private String createdId;
#Column(name = "updated_id", length = 50)
private String updatedId;
#Column(name = "created_date", columnDefinition = "timestamptz", nullable = false)
private OffsetDateTime createdDate;
#Column(name = "last_modified_date", columnDefinition = "timestamptz")
private OffsetDateTime lastModifiedDate;
}
I have a PutMapping Spring boot API that gets below Json Input
{
"rewardId": 53,
"rewardTitle": "Reward is Allocated",
"rewardText": "Reward allocated for your recent purchase with our shop located at ABC-Mall",
"rewardType": "Informational",
"rewardFor": "Customer",
"rewardFromDate": "2019-04-12T00:00:00+05:30",
"rewardToDate": "2019-04-15T00:00:00+05:30",
"isDisplayOn": false
}
My Controller takes Principal object for both creation and updating the rewards table
#PutMapping
public ResponseEntity<RewardsResponse> updateRewards(Principal updatedPrincipal,
#RequestBody RewardUpdateRequest RewardUpdateRequest) {
But I won't send my createdId or updatedId from my Angular-UI.. So when i try to insert the updated-entity in to the table, using the below service-layer code
public RewardEntity updateReward(Principal principal, rewardEntity rewardEntity) {
String updatedId = null != principal ? principal.getName() : "defaultUpdatedId";
rewardEntity.setUpdatedCdsId(updatedId);
rewardEntity.setLastModifiedDate(OffsetDateTime.now());
return rewardRepository.save(rewardEntity);
}
I get the below error
could not execute statement; SQL [n/a]; constraint [created_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
My assumption is that RewardEntity gets updated in the same row by mapping the ID that we pass and update only the fields that i set and do not touch rest of the fields ...
Should i first get my RewardEntity object from the DB based on the ID and then update on top of it ?? This makes the code connect DB twice for every update.
Request your inputs please
I would first get reference object using updatedId
RewardEntity rewardEntity = rewardRepository.getOne(updatedId )
update this object as per your requirement
rewardEntity.setLastModifiedDate(OffsetDateTime.now());
and finally use save to update this.
return rewardRepository.save(rewardEntity);
getOne() returns a reference to the entity and internally invokes EntityManager.getReference() method. It will always return a proxy without hitting the database (lazily fetched).

Oracle - JPA Alter Constraint

I'm having a problem with a constraint in oracle/jpa mapping.
I have these 4 tables
SucursalSolicitanteFornecimento(
Sucursal_ID (NUMBER) PK
Sucursal_Name(VARCHAR)
Sucursal_UF
);
ParamSolicitante(
paramID (NUMBER)
paramName(Varchar)
);
ConfiguracaoParametroSolicitante(
paramConfigID (NUMBER) PK,
paramID FK (from ParamSolicitante),
requestID (From SucursalSolicitanteFornecimento)
);
ConfiguracaoParametroSolicitanteSucursal(
ID (NUMBER) PK,
paramConfigID FK from ConfiguracaoParametroSolicitante,
SucursalSolicitanteFornecimento_ID
SucursalSolicitanteFornecimento_UF - Both From SucursalSolicitanteFornecimento
);
My problem is when I try to insert a new value in ConfiguracaoParametroSolicitanteSucursal I can't have 2 different ParamConfigID with the same SucursalSolicitanteFornecimento_UF and same SucursalSolicitanteFornecimento_ID because of the FK.
But now that's exactly what I need, more than one parameter for that SucursalSolicitante on the same state.
My Java class is mapped this way:
public class ConfiguracaoParametroSolicitanteSucursal{
#Id
#GeneratedValue()
private Integer codigoConfigParamSolctteSuc;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns( { #JoinColumn(name = "SUCURSAL_ID", referencedColumnName = "SUCURSAL_ID", nullable = false),
#JoinColumn(name = "SUCURSAL_UF", referencedColumnName = "SUCURSAL_UF", nullable = false) })
private SucursalSolicitanteFornecimento sucursalParametro;
#JoinColumn(name = "paramConfigID", referencedColumnName = "paramConfigID", nullable = false)
#ManyToOne(fetch = FetchType.LAZY)
private ConfiguracaoParametroSolicitante configuracaoParametroSolicitante;
}
What can I do to achieve what I need? I was wondering if I have to change #OneToOne column to #ManyToOne and Delete the Constraint on my Database.

null id generated for composite PK

I have the following tables and the following relationship table too: , which has a composite PK as follow:
UserRole.java
#RooJavaBean
#RooJpaEntity(identifierType = UserRolePK.class, versionField = "", table = "UserRole", schema = "dbo")
#RooDbManaged(automaticallyDelete = true)
#RooToString(excludeFields = { "idApplication", "idRole", "idUserName" })
public class UserRole {
}
UserRole_Roo_DbManaged.aj
#ManyToOne
#JoinColumn(name = "IdApplication", referencedColumnName = "IdApplication", nullable = false, insertable = false, updatable = false)
private Application UserRole.idApplication;
#ManyToOne
#JoinColumn(name = "IdRole", referencedColumnName = "IdRole", nullable = false, insertable = false, updatable = false)
private Role UserRole.idRole;
#ManyToOne
#JoinColumn(name = "IdUserName", referencedColumnName = "IdUserName", nullable = false, insertable = false, updatable = false)
private Users UserRole.idUserName;
But also exist a PK table:
#RooIdentifier(dbManaged = true)
public final class UserRolePK {}
And its identifier class (UserRolePK_Roo_Identifier.aj)
privileged aspect UserRolePK_Roo_Identifier {
declare #type: UserRolePK: #Embeddable;
#Column(name = "IdRole", nullable = false)
private Long UserRolePK.idRole;
#Column(name = "IdUserName", nullable = false, length = 16)
private String UserRolePK.idUserName;
#Column(name = "IdApplication", nullable = false)
private Long UserRolePK.idApplication;
The way how I'm setting the service objec to save is:
UserRole userRole= new UserRole();
userRole.setIdApplication(app);
userRole.setIdRole(invited);
userRole.setIdUserName(user);
appService.saveURole(userRole);
app has been set and saved before (same transaction), as well as invited and user objects.
Since user (from Users table with composite PK: IdUserName which is a String ), is defined as follow, otherwise doesnt work.
#RooJavaBean
#RooJpaEntity(versionField = "", table = "Users", schema = "dbo")
#RooDbManaged(automaticallyDelete = true)
#RooToString(excludeFields = { "quotations", "taxes", "userRoles", "idCompany", "idPreferredLanguage" })
public class Users {
#Id
//#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "IdUserName", length = 16, insertable = true, updatable = true)
private String idUserName;
}
So, the error that I'm getting is:
org.springframework.orm.jpa.JpaSystemException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.domain.UserRole; nested exception is javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.domain.UserRole
Try this:
public class UserRole {
#PrePersist
private void prePersiste() {
if (getId() == null) {
UserRolePK pk = new UserRolePK();
pk.setIdApplication(getIdApplication());
pk.setIdRole(getIdRole);
pk.setIdUserName(getIdUserName());
setId(pk);
}
}
}
Roo is generating the fields on UserRole entity and its id embedded class, but is not the same thing (UserRole.idRole is not the same than UserRole.id.idRole). In your example, you fill the UserRole fields, but not the id fields. This code makes it for you before entity is persisted.
Good luck!
In my case if the follow example tries to be persisted in DB, then similar Exception mentioned above is thrown:
EntityExample e = new EntityExample();
repositoryExample.save(e);
//throw ex
This is caused due to missing id field values which needs to be set something like that:
EntityExample e = new EntityExample();
e.setId(new EmbeddedIdExample(1, 2, 3));
repositoryExample.save(e);

Resources