I have 3 tables table A,Table B and table A_B. Table A_B has columns
|A_id | B_id | Updated_date|
| | | |
I have to fetch all the A_ids based on given single B_id which will eventually be a
List<A_B objects> based on given input of b_id.
Now using each element of this List<A_B objects> i have to query table_A and get individual records, add all those record and return a Page with given PageRequest.
Generally , using repo.findAll(SpecificationBuilder,PageRequest) is used for a single table and it returns a complete list. In my case it will be a single record and i have to hit repo again and again to get a List. How can i solve this issue where i can add each record to my Page model and return it.
I tried creating a Page from the List<A_object> but the whole purpose of Page is sending the PageRequest directly to JPA and not make Page in Java or have a sublist. In my case i will get individual records and i need to add all those and sent back as Page.
In my Spring Boot app, every hour I'm getting new Parent and Child data from a response that I need to update the db with. The response can contain new Parents, new Children, existing Parents with updates and if a Parent and/or Child is not present in the response, they need to be deleted from db.
This all needs to happen in a single transaction to avoid race conditions.
I have a Parent table like below:
Parent table
id | name | age | email | child_id
----------------------------------
And a Child table with a foreign key to Parent:
Child table
id | parent_id | name
---------------------
And a Mapping table
id | parent_id | child_id
-------------------------
1.) First approach is to insert on duplicate 'name' update all new Parent data, then insert the new Child data, then delete all Child data that is not contained in the response, then finally delete all Parent data that is not contained in the response
Something like:
//For each new parent run native query
INSERT INTO PARENT (name) VALUES (name, age, email)
ON DUPLICATE KEY
UPDATE name=name, age=age, email=email;
//For each new child insert with native query
INSERT INTO CHILD (name) VALUES (name)
ON DUPLICATE KEY
UPDATE name=name
//Remove old Child data
DELETE FROM CHILD WHERE name not in (new_child_name_list);
//Remove old Parent Data
DELETE FROM Parent WHERE name not in (new_parent_name_list);
To speed things up and batch, I'd be maintaining my own transaction in the EntityManager and running the above sequence. I'm worried about performance and if some of these operations can be combined. I also don't know how I could set the mapping just using native queries
2.) I get all data from db and do all the logic in Java myself.
public void refresh(List<Parent> newParents, List<Child> newChildren) {
//EntityManager transaction logic here
List<ParentEntity> oldParents = parentRepo.findAll();
List<ChildEntity> oldChildren = childRepo.findAll();
for(ParentEntity oldParent : oldParents) {
if (/* old matches a new parent */) {
//update matching old parent to new parent
//add associated children to parent before save
} else {
// no match, delete old parent
}
}
}
Is it better to have several sql operations with native queries, or handle the majority of the work in java?
EDIT: Many-to-many relationship clarity
I believe you can take cascading operation with Hibernate or other ORM tool easily. I don't think it is a good idea to sync in DB side as well.
please refer to here
Query q = session.createQuery("from Stock where stockCode = :stockCode ");
q.setParameter("stockCode", "4715");
Stock stock = (Stock)q.list().get(0);
session.delete(stock);
I have a many-to-many relationship between two entities:
Entity A
ID
NAME
Entity B
ID
NAME
Join Table A_B
A_ID
B_ID
I'm trying to do an insert based off of the secondary unique attributes (names). I want to avoid fetching each entity by their name and then saving (2 queries & 1 insert vs 1 insert). Effectively, I'm looking to do something like this:
#Query(nativeQuery = true, value = "INSERT INTO A_B(A_ID, B_ID) VALUES ((SELECT ID FROM A WHERE NAME = ?), (SELECT ID FROM B WHERE NAME = ?))")
void addToJoinTable(String nameA, String nameB);
I saw this post, but I'm getting exceptions because an insert doesn't return a result set. The post mentions setting nativeQuery to true would resolve the issue, but I've had no such luck... Is there another & better way to do this?
Disclaimer: I am new to JPA, so this might be a stupid question...
I use the postgresql database.
I have a serial field in one table, the serial field is not primary key, and it is used to allocate port number to avoid duplication, the serial starts from 30001 for example.
I use hibernate to insert table, but the field is always 0.
My sequence description:
last_value | bigint | 30001
start_value | bigint | 30001
increment_by | bigint | 1
max_value | bigint | 9223372036854775807
min_value | bigint | 30001
and my entity java code is:
#Basic
#Column(name = "XXX")
#SequenceGenerator(name="XXX_seq",sequenceName="XXX_seq", allocationSize=1, initialValue = 30001)
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="XXX_seq")
Because the field is not primary key, so I don't use the #Id.
How should I do?
As #GeneratedValue can only be used with #Id (answered earlier by tom)
Provides for the specification of generation strategies for the values
of primary keys. The GeneratedValue annotation may be applied to a
primary key property or field of an entity or mapped superclass in
conjunction with the Id annotation.
DOC
Solution
You can get max value by query like: it returns you the max used port
Query query = em.createQuery("select max(u.portId) from XXXX u");
List<Long> list = query.getResultList();
return (list.isEmpty() || list.get(0) == null ? maxId : list.get(0));
#GeneratedValue
only works on identifiers (#Id)
You could use:
an Insert Trigger
a separate Table where you store the last used number
Database specific solution (Postgres can have a Sequence on a Column if i'm not mistaken)
Not really JPA but I don't think that can be done automagically by JPA/Hibernate
I'm having an Entity which has a primary key / id field like the following:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
This works well. I'm using EclipseLink to create the DDL-Schema, and the column is correctly created like so:
`id` bigint(20) NOT NULL AUTO_INCREMENT
However, I've got several entities for which I do want to specify the PK myself (it's a little application that transfers data from an old database to the new one we're building). If I specify the ID for the POJO (using setId(Long id)) and persist it, EclipseLink does not save it (i.e. the record is saved, but the id is auto generated by eclipseLink).
Is there a way to manually specify the value of a column which has a #GeneratedValue ?
Here some thoughts on the issue:
I tried to work around the problem by not using #GeneratedValue at all, but simply manually define the column to be AUTO_INCREMENTed. However this forces me to manually provide an IDs always, since EclipseLink validates the primary key (so it may not be null, zero, or a negative number). The exception message reads that I should specify eclipselink.id_validation, however this does not seem to make any difference (I annotated #PrimaryKey(validation = IdValidation.NONE) but still got the same message).
To clarify: I'm using EclipseLink (2.4.0) as persistence provider and I can't switch away from it (large portions of the project depend on eclipselink specific query hints, annotations, and classes).
EDIT (In Response to the answers):
Custom Sequencing: I tried to implement my own sequencing. I tried subclassing DefaultSequence, but EclipseLink will tell me Internal Exception: org.eclipse.persistence.platform.database.MySQLPlatform could not be found. But I've checked: The class is on the classpath.
So I subclassed another class, NativeSequence:
public class MyNativeSequence extends NativeSequence {
public MyNativeSequence() {
super();
}
public MyNativeSequence(final String name) {
super(name);
}
#Override
public boolean shouldAlwaysOverrideExistingValue() {
return false;
}
#Override
public boolean shouldAlwaysOverrideExistingValue(final String seqName) {
return false;
}
}
However, what I get is the following:
javax.persistence.RollbackException: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.java:102)
...
Caused by: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
at org.eclipse.persistence.exceptions.ValidationException.nullPrimaryKeyInUnitOfWorkClone(ValidationException.java:1451)
...
(stack trace shortened for clarity). This is the same message which I got before. Shouldn't I subclass NativeSequence? If so, I don't know what to implement for the abstract methods in Sequence or StandardSequence.
It may also be worth noting, that simply subclassing (without overriding any methods) the class works as expected. However, returing false in shouldAlwaysOverrideExistingValue(...) will not generate a single value at all (I stepped through the program and getGeneratedValue() is not called once).
Also, when I insert like 8 entities of a certain kind within a transaction it resulted in 11 records in the database (what the hell?!).
EDIT (2012-09-01): I still do not have a Solution for the problem, Implementing my own sequence did not solve it. What I need is a way to be able to not set an Id explicitly (so it will be auto generated) and to be able to set an Id explicitly (so it will be used for the creation of the record in the database).
I tried to define the column as auto_increment myself and ommit #GeneratedValue, however Validation will kick in and not allow me to save such an entity. If I specify a value != 0 and != zero, mysql will complain for a duplicate primary key.
I'm running out of ideas and options to try. Any? (starting a bounty)
This works with eclipselink. It will create a seperate table for the sequence, but that shouldn't pose a problem.
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id", insertable=true, updatable=true, unique=true, nullable=false)
private Long id;
GenerationType.AUTO will choose the ideal generation strategy. Since the field is specified as insertable and updateable, a TABLE generation strategy will be used. This means eclipselink will generate another table holding the current sequence value and generate the sequence itself instead of delegating it to the database. Since the column is declared insertable, if id is null when persisting, eclipselink will generate the id. Otherwise the existing id will be used.
If you use TABLE sequencing, then EclipseLink will allow you to override the value (or SEQUENCE if your database supports this, MySQL does not).
For IDENTITY, I'm not even sure that MySQL will allow you to supply your own Id, you might want to verify this. In general I would never recommend using IDENTITY as it does not support preallocation.
There are a few issues with allowing IDENTITY to provide the id or not. One is that two different insert SQL will need to be generated depending on the id value, as for IDENTITY the id cannot be in the insert at all. You may wish to log a bug to have IDENTITY support user provided ids.
You should still be able to get it working with your own Sequence subclass, or possibly MySQLPlatform subclass. You would set your MySQLPlatform subclass using the "eclipselink.target-database" persistence unit property.
Database-centric solution to your problem:
Create an auxiliary, nullable column in your table. It will hold your manually assigned ids:
CREATE TABLE `test_table`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`manual_id` bigint(20) NULL,
`some_other_field` varchar(200) NOT NULL,
PRIMARY KEY(id)
);
Map this column to a normal field in your Entity:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="manual_id")
private Integer manualId;
Create a trigger that sets the table id to the manual assigned id if it is not null:
DELIMITER //
CREATE TRIGGER `test_table_bi` BEFORE INSERT ON `test_table`
FOR EACH ROW
BEGIN
IF NEW.`manual_id` IS NOT NULL THEN
SET NEW.`id` = NEW.`manual_id`;
END IF;
END;//
DELIMITER;
Always use the manualId when you need to assign a custom id. The trigger will do the magic for you:
testEntiy.setManualId(300);
entityManager.persist(testEntity);
After the database import phase, simple remove the trigger, the auxiliary column and it's mapping.
DROP TRIGGER `test_table_bi`;
ALTER TABLE `test_table` DROP COLUMN `manual_id`;
Warning
If you manually specify an id greater than the current AUTO_INCREMENT value, the next generated id will jump to the value of the manually assigned id plus 1, e.g.:
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (50, 'Something');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (90, 'Something 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (40, 'Something 3');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 3');
Will wield the results:
+----+-----------+------------------+
| id | manual_id | some_other_field |
+----+-----------+------------------+
| 50 | 50 | Something |
| 51 | NULL | Something else |
| 90 | 90 | Something 2 |
| 91 | NULL | Something else 2 |
| 40 | 40 | Something 3 |
| 92 | NULL | Something else 3 |
+----+-----------+------------------+
To avoid problems it is highly recommended to set the AUTO_INCREMENT column to start with a number greater than all of the existing ids in your previous database, e.g.:
ALTER TABLE `test_table` AUTO_INCREMENT = 100000;
I might be missing something obvious, but why not just define another Entity with the same #Table(name=".....") annotation, with the same fields, but make the id not generated? Then you can use that Entity for the code that copies data from the old DB to the new, and the one with the generated Id can be used for normal creates that require id generation.
I can't tell you if it works with EclipseLink, but we're using Hibernate here and it doesn't seem to mind it.
Using GenerationType.SEQUENCE with PostgreSQL and EclipseLink worked for me.
1) Change
#GeneratedValue(strategy = GenerationType.IDENTITY)
by
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="step_id_seq")
#SequenceGenerator(name="step_id_seq", sequenceName="step_id_seq")
Now, you can call sequence using NativeQuery:
return ((Vector<Integer>) em.createNativeQuery("select nextval('step_id_seq')::int").getSingleResult()).get(0);
and set the returned Id to your Entity before call EntityManager.persist() method.
Hope it's not too late!
Look for Custom Id Generator
http://blog.anorakgirl.co.uk/2009/01/custom-hibernate-sequence-generator-for-id-field/
maybe this could help.
My way (MySql) is deactivate GeneratedValue:
#Id
//#GeneratedValue
#Column(unique = true, nullable = false, columnDefinition ="BINARY(16)")
private UUID id;
And add in Entity:
#PrePersist
protected void onCreation() {
if (id == null) setId(UUID.randomUUID());
}
Now in my code I can do (on service for example):
String clientID = env.getParam("id");
Entity entity = entityRepository.findFirstById(UUID.fromString(clientID));
//is new?
if (entity==null){
entity = new Entity();
entity.setId(UUID.fromString(clientID));//set cumstom ID here
}
Entity entityNew = entityRepository.save(entity); //insert
if (entityNew.getId().equals(entity.getId()) ){
Log.i("OK!")
}
👌