Implementing complex MAX function in Spring Boot/JPA query language - java

Spring Boot here using JPA/Hibernate and CrudRepository impls for managing persistence to my DB tables.
I have the following MySQL table:
CREATE TABLE IF NOT EXISTS price_scarcity_configs (
price_scarcity_config_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
price_scarcity_config_ref_id VARCHAR(36) NOT NULL,
price_scarcity_config_version BIGINT NOT NULL,
price_scarcity_config_updated_on DATETIME NOT NULL,
price_scarcity_config_fizz INTEGER NOT NULL,
CONSTRAINT pk_price_scarcity_configs PRIMARY KEY (price_scarcity_config_id),
CONSTRAINT uc_price_scarcity_configs_ref_id_and_version UNIQUE (price_scarcity_config_ref_id, price_scarcity_config_version)
);
These records will be versioned and different versions of the "same" record will all share the same price_scarcity_config_ref_id. Hence 2+ records can have the same price_scarcity_config_ref_id but will have two distinct different versions.
I'm also using the following JPA/Hibernate entity to model it:
// Uses Lombok annotations to generate getters/setters, etc.
#MappedSuperclass
#Data
#EqualsAndHashCode(callSuper=false)
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String refId;
}
#Entity
#Table(name = "price_scarcity_configs")
#AttributeOverrides({
#AttributeOverride(name = "id", column = #Column(name = "price_scarcity_config_id")),
#AttributeOverride(name = "refId", column = #Column(name = "price_scarcity_config_ref_id"))
})
#Data
#EqualsAndHashCode(callSuper=false)
public class PriceScarcityConfiguration extends BaseEntity {
#Column(name = "price_scarcity_config_version")
private Long version;
#Column(name = "price_scarcity_config_updated_on")
private Date updatedOn;
#Column(name = "price_scarcity_config_fizz")
private Integer fizz;
}
I am now trying to write the PriceScarcityConfigurationRepository and need a fairly sophisticated query. Given a refId, I need to find the record who matches that ref id and has the highest/max version number. The raw SQL query to perform this is:
select
*
from
price_scarcity_configs pcs
inner join
(
SELECT
price_scarcity_config_ref_id,
MAX(price_scarcity_config_version) as max_ver
FROM
price_scarcity_configs
group by
price_scarcity_config_ref_id
) t
on
t.price_scarcity_config_ref_id = pcs.price_scarcity_config_ref_id
and
t.max_ver = pcs.price_scarcity_config_version;
Given my repository and using JPA/Hibernate's built-in query language/annos, how do I implement this query?
public interface PriceScarcityConfigurationRepository extends CrudRepository<PriceScarcityConfiguration,Long> {
#Query("FROM PriceScarcityConfiguration WHERE ??? HOW TO IMPLEMENT THE ABOVE QUERY HERE ???")
PriceSheetConfiguration fetchLatestVersionByRefId(#Param("refId") String refId);
}

You could use the following query instead and use setMaxResults(1)
FROM PriceScarcityConfiguration p WHERE p.refId = :refId ORDER BY p.version DESC
Or simply use the Spring Data notation
List<PriceSheetConfiguration> findFirstByRefIdOrderByVersionDesc(String refId);

Related

Representing #EmbeddedId as SQL for H2 database

I am currently working on a Java project with Hibernate entities (more below). In order to test my data access object layers, I am using H2 database to populate an in-memory database and throwing queries at it. Until this point, everything is fine.
However, the problem comes when simulating the #EmbeddedId annotation.
#Entity
#Table(name = "BSCOBJ")
public class BasicObject extends AbstractDomainObject {
#EmbeddedId // This annotation here
private RestrainPK restrain;
#Embeddable
public static class RestrainPK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "CODI", nullable = false)
private String coDi;
#Column(name = "COGA", nullable = false)
private String coGa;
#Column(name = "TYOR", nullable = false)
private String tyOr;
public RestrainPK() {
}
... // Getters and setters
}
}
"Simply" creating the table BSCOBJ and populating it gives no value when fetching data (of course, I checked that the request would give result "normally"). How do I represent this nested class in a SQL table creation / value insertion request ? Is that even possible ?
Thanks in advance,
EDIT
As requested, here is some samples about the SQL / Hibernate ran.
Creation request:
CREATE TABLE BSCOBJ (CODI VARCHAR(5) NOT NULL, COGA VARCHAR(5) NOT NULL, TYOR VARCHAR(5) NOT NULL);
Insertion request:
INSERT INTO BSCOBJ (CODI, COGA, TYOR) VALUES
('HELLO', 'MAT', 'REF'),
('BONJ', 'SOME', 'DAIL'),
('SOPA', 'KDA', 'RATIO');
Request given by Hibernate when trying to run the test code:
select r.restrain.tyOr from mypackage.BasicObject r where r.restrain.coDi = :coDi and r.restrain.coGa = :coGa
With the following values:
coDi = "BONJ";
coGa = "SOME";
Throws a NoResultException. I am expecting DAIL, from the second line of the INSERT request.
I have used #EmbeddedId only one time, but I think that you need #AttributeOverrides under your #EmbeddedId
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "idpk", column = #Column(name="IDPK", nullable = false),
#AttributeOverride(name = "code", column = #Column(name="CODE")
})
and remove your #Column annotations from FormulePK

JPA mapping 2 entity table with a relationship table between them

I'm trying to map these two entitiy tables to one model class, game and platform. A game can have multiple platforms. So in my db I have the following tables:
CREATE TABLE IF NOT EXISTS games (
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR,
summary TEXT,
avg_score REAL,
);
CREATE TABLE IF NOT EXISTS platforms (
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR
);
And a relationship table called Game_Platforms
CREATE TABLE IF NOT EXISTS game_platforms (
id serial not null primary key,
game_id INTEGER NOT NULL,
platform_id INTEGER NOT NULL,
release_date date not null,
FOREIGN KEY (game_id) REFERENCES games (id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (platform_id) REFERENCES platforms (id) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE(game_id,platform_id, release_date) --The same game can be released for the same platform multiple times (i.e. remaster)
);
Note that the tables have more columns but I'm just showing the ones that are relevant to this problem.
So on my model I have the following class which I want to map to the db using JPA
#Entity
#Table(name = "games")
public class Game {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "games_gameid_seq")
private long id;
#Column(length = 100, nullable = false, unique = true)
private String name;
//Map with platforms
private Map<String, LocalDate> platforms;
And platform which doesn't have a class because I didn't find it necessary. I don't think a #OneToMany annotation will be enough to map this. Adding a class "Platform" to my model would be of last resort as I would have to change most of my interfaces, needing to change the whole app. Any idea on how to aproach this? Thanks in advance!
When using an Object Relational Mapping tool such as Hibernate / the JPA API, you should be thinking in an object relational model - keyword: object. Thus not wanting to map a table as an entity is very counterproductive to your needs as you won't be able to use the technology as intended; you'd have to resort to using native queries.
Instead, do map all tables involved (GamPlatform in this case) and actually put JPA to work by adding the relational mappings.
#Entity
public class Game {
...
#OneToMany(mappedBy="game")
private Set<GamePlatform> gamePlatforms;
...
}
The real problem to be solved is the fact that there is a large pile of existing code which will break because of this change. To temporarily deal with that you could maintain the existing getter for the platforms property as it is and under water build up the returned HashMap from the newly defined model. Assuming the getter is named getPlatforms():
#Deprecated
public Map<String, LocalDate> getPlatforms(){
Map<String,LocalDate> ret = new HashMap<>();
gamePlatforms.stream().forEach(gamePlatform -> {
ret.put(gamePlatform.getPlatform().getName(), gamePlatform.getReleaseDate());
});
return ret;
}
You could mark that method as deprecated and over time migrate code to use the proper entity classes instead.
Footnote: I'm sure there is an even compacter Java 8 way to do the above using Collectors.toMap, that is left as an exercise to the reader.
if I understand your meaning correctly you have one game and each game have several platform!
you need onToMany relation and need to creat platform model.
look at this example
Game.java
#Entity
#Table(name = "games")
public class Game {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "games_gameid_seq")
private long id;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy="game_name")
#Column(name = "PlatFormId", nullable = false)
private Set<PlatForm> PlatFormId= new HashSet<PlatForm>(0);
}
PlatForm.java
#Entity
#Table(name = "platForm")
public class PlatForm {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "games_gameid_seq")
private long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "game_name", nullable = false)
private Game game;

How to make EclipseLink not generate primary keys and let that to the Database

I use Liquibase to create the DDL schema of the database(Derby). Also, I use JPA and EclipseLink and I want to make EclipseLink so that it does not insert any values for primary keys and I want them to be generated through the pure sql. Now, I`ve tried to remove the generation type strategy in the entities, but it is trying to insert null values for PKs to the tables, which are not allowed for PKs.
I`ll be glad if you help me.
Now I have this, but it gives me the exception below.
#Entity
#XmlRootElement
#Table(name = "ROLE")
public class Role implements Serializable {
private static final long serialVersionUID = 4736444799522006644L;
# Id
# JsonIgnore
# GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
AND
CREATE TABLE Role (
id BIGINT PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
role VARCHAR(255)
);
Exception Description: The attribute [id] of class [...] is mapped to a primary key column in the database. Updates are not allowed.
You could create a sequence for generating IDs (instead of this GENERATED BY DEFAULT START WITH 1, INCREMENT BY 1) . Its' a better approach and after that you can use your sequence in JPA:
...
#Entity
public class Employee {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="EMP_SEQ")
#SequenceGenerator(name="EMP_SEQ", sequenceName="EMP_SEQ", allocationSize=100)
private long id;
...
}
You can use sequence in SQL <sequence>.NEXTVAL to get new ID value.
For more info - please check https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing
Unfortunately this method won't work if your DB does not support sequences.
Or for your Derby DB you can try this (if primary key is generated as identity)
#Entity
public class EntityWithIdentityId {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
....
}

How to create hibernate composite key and get values from table

I am trying to use hibernate annotations for getting data from a MySQL database table which doesn't have a primary key defined.
However the fact is 2 columns of that table together are unique in the table. How can I achieve the same using hibernate annotation?
This is my code
#Entity
#Table(name = "coc_order_view")
public class CoCOrderDetailsTest {
#EmbeddedId
private MyJoinClassKey key;
#Column(name = "coupon_code")
private String couponCode;
some other columns and their getters and setters .....
#Embeddable
public class MyJoinClassKey implements Serializable {
private static final long serialVersionUID = -5L;
#Column(name = "product_id")
private int productId;
#Column(name = "order_id")
private int orderId;
gettes and setters....
And here is my criteria query
Criteria criteria = getHibernatetemplate().getSession().createCriteria(CoCOrderDetailsTest.class);
criteria.add(Restrictions.eq("status", "New"));
ArrayList<CoCOrderDetailsTest> orderDet = (ArrayList<CoCOrderDetailsTest>) getHibernatetemplate().get(criteria);
I am unable to get all the values from db. Kindly suggest some solutions.
After reading through your question again not sure this will help. You can't have a table without primary key(s). Read the first couple of paragraphs in this article
That said, if you can alter the table and add primary keys on those fields you need to add #IdClass annotation to your class signature for CoCOrderDetailsTest and then get rid of the #embeddable and #embeddedId notation in your classes.
Another alternative, if you can add a field to the table, would be to use an #GeneratedValue on that added primary key field and of course annotate it with #Id.
If you can't alter the table then you can't use JPA and you'll have to use JDBC.
See http://docs.oracle.com/javaee/5/api/javax/persistence/IdClass.html
A working example:
#Entity
#Table(name = "player_game_log")
#IdClass(PlayerGameLogId.class)
public class PlayerGameLog {
#Id
#Column(name = "PLAYER_ID")
private Integer playerId;
#Id
#Column(name = "GAME_ID")
private String gameId;
....
and the id class (note there are no annotations on the id class)....
public class PlayerGameLogId implements Serializable {
private static final long serialVersionUID = 1L;
private Integer playerId;
private String gameId;
Try:
String hql = "FROM CoCOrderDetailsTest WHERE status = :status";
Query query = session.createQuery(hql);
query.setParameter("status","New");
List results = query.list();
I usually use EntityManager rather than session so I'm not familiar with this syntax - and I have typically added a type to the list to be returned - like:
List<CoCOrderDetailsTest> results = query.list();

Spring Data JPA "null value in column xxx violates not-null constraint" on serial column with postgresql

My entity has a mapOrder field which I want auto-increment like below:
#Entity
public class Map{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(columnDefinition = "serial")
private Long mapOrder;
//.......
}
The sql generated seems good:
CREATE TABLE map
(
id bigserial NOT NULL,
map_order serial NOT NULL,
...
)
But when I save it with Spring Data JPA's repository, like this:
Map m=new Map();
repo.save(m);
will give me exception:
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "map_order" violates not-null constraint
Any ideas?
Try changing your code to this:
#GeneratedValue(strategy = GenerationType.SEQUENCE)
Reference: https://stackoverflow.com/a/29028369
#GeneratedValue works with identifiers and you can't use it with regular fields.
You can, for example, use some object for sequences (with any key generation strategy):
#Entity
public class JpaNoPkSequence {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id", nullable=false, updatable=false)
private Long id;
}
For using the strategy GenerationType.SEQUENCE the sequence should be created in the database:
CREATE SEQUENCE JPA_PK_SEQ START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;
ALTER SEQUENCE "JPA_PK_SEQ" OWNER TO something;
This should be specified in the key definition. You should also add a one-to-one relationship with the object that you will use to obtain sequences for regular fields:
#Entity
public class Map {
#Id
#SequenceGenerator(name="jpaPkSeq", sequenceName="JPA_PK_SEQ", allocationSize=1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jpaPkSeq")
#Column(name = "id", nullable=false, updatable=false)
private Long id;
#OneToOne
private JpaNoPkSequence sequence;
...
}
Hope this helps.
I had the same problem. In my case, the error was in creating the table.
You need to use SERIAL type instead of others (at least in Postgres).
Before (bigint type)
CREATE TABLE IF NOT EXISTS sample_table
(
id BIGINT NOT NULL,
...
);
After (serial type)
CREATE TABLE IF NOT EXISTS sample_table
(
id SERIAL NOT NULL,
...
);
The id in the entity class looked like this:
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
This question is full of bad advice. The problem is the second column with a serial value. The problem is that Hibernate explicitly inserts a null there. You have to tell it not to.
#Column(columnDefinition = "serial", insertable = false)
private Long mapOrder;

Categories