I am using hibernate with restful interface, I have one-to-many relationship between three tables, when I want to retrieve the records in those tables from mysql database (using curl command) I also get their ids (which were auto-generated) but I don't want them to be retrieved, is there any way I can do this?
Here is result I retrieve:
[
{
"deviceID": "no id",
"deviceName": "no name",
"deviceType": "no_type",
***"id": 1,***
"newMeasurements": [
{
"measurementT": "type",
"measurementU": "unit",
"measurementV": "value",
"measurementDateTime": "15.09.9898",
***"id": 1***
}
],
"Oldmeasurement": [
{
"measurementTime": "12.02.1586",
"sampleR": "rate",
"sampleU": "Unit",
"sampleT": "Type",
"sampleU": "Unit",
"sampleV": "noValues",
***"id": 1***
}
],
***"patientID": 1***
}
]
Before answering to your question I would like to give you a glance/background on few things.
In case of OOP, Almost everything is object and how to identify, compare two instance of same type plays a key role here. There is a concept called equality and identity which is use to identify and compare objects(I hope you are aware of it).
Similarly there is another mechanism in hibernate called database identity to identify/compare two same type instance. Meaning it is a way to determine that the two same type instances representing/pointing to same row. This can be achieved only through identifier which you want to ignore during retrieval.
The bottom line is Id is really mandatory to identify objects since hibernate compares objects based on values but not by its memory location (identity)
Recommended way to create your entity is
Class MyEntity
{
#id
#column(name=”id”)
//Id generation strategy goes here.
Private int id;
#column(name=”name”)
Private String name;
Public int getId()
{
return this.id;
}
//Getter and setters of name
}
You might have noticed the absence of setter method of ID. Reason is changing of identity is not allowed/recommended. Hope this is helpful!
Related
I have a Spring application and in one of the Controllers lies a POST method that allows a user to create multiple entities at once, since they're all related with one main entity, EncodingResult.
After testing a bit, it seemed that this caused the following error: object references an unsaved transient instance. Looking around a bit, I found that the solution to it was to use CascadeType.ALL on the attributes that have #JoinColumn annotations in this main class, as such:
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "codec", referencedColumnName = "commit_hash", nullable = false)
private Codec codec;
The Codec side is as follows:
(...)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "codec")
private Set<EncodingResult> associatedResults = new HashSet<>();
#Column(name = "commit_hash", nullable = false, unique = true)
// indicates the precise version of the codec
private String commitHash;
(...)
This, however, seems to cause another error: PSQLException: ERROR: duplicate key value violates unique constraint "uk_paitxovhx3j95tuaq9krkk9qh" Detail: Key (commit_hash)=(ec44ee022959410f9596175b9424d9fe1ece9bc8) already exists.
To my understanding, this must be because the CascadeType.PERSIST tries to update the related entities when the parent is updated no matter what (which would make sense, given that in the first POST attempt the system works fine, but in the subsequent ones, which do not save Codec, for example, if there is already one such entity with the given commitHash it doesn't), but I am not sure if this is correct.
Either way, how can I make sure that this operation succeeds? Is it even possible to do what I am trying to do here?
Below is the JSON body being used:
{
"codec": {
"name": "vvcodec",
"commitHash": "ec44ee022959410f9596175b9424d9fe1ece9bc8",
"codecAttrs": "--threads 1",
"preset": ""
},
"video": {
"name": "bowing_cif",
"resolution": "352x288",
"fps": 29.97,
"nFrames": 300,
"format": "y4m",
"uniqueAttrs": "fps29.97_nf300_fy4m_r352x288_nbowing_cif"
},
"encodingConfig": {
"uniqueAttrs": "qp27_nf30_nt8_ca",
"qp": 27,
"nFrames": 30,
"nThreads": 8,
"codecAttrs": ""
},
"ypsnr": 48.2012,
"upsnr": 49.5337,
"vpsnr": 51.1360,
"yuvpsnr": 48.749,
"bitrate": 196.1958,
"time": 0.595,
"energyConsumption": 78.32
}
EDIT: I suppose this will also help - my implementation of the #Service layer for the given method:
public EncodingResult saveEncodingResult(EncodingResult encodingResult) {
var video = encodingResult.getVideo();
var encodingConfig = encodingResult.getEncodingConfig();
var codec = encodingResult.getCodec();
if (videoRepository.findByUniqueAttrs(video.getUniqueAttrs()).isEmpty())
videoRepository.save(video);
if (encodingConfigRepository.findByUniqueAttrs(encodingConfig.getUniqueAttrs()).isEmpty())
encodingConfigRepository.save(encodingConfig);
if (codecRepository.findByCommitHash(codec.getCommitHash()).isEmpty())
codecRepository.save(codec);
return encodingResultRepository.save(encodingResult);
}
Do you receive EncodingResult in your controller method? If so, hibernate has no idea that Codec already exists because you're creating a new Codec object every time (well, spring does, by deserializing json). Upon save, hibernate tries to insert new Codec because of CascadeType.ALL instead of using an existing entity.
Typically, you would fetch an existing entity from the database, update it's state and then save it - that is unless you're creating a new entity.
Second thing, if EncodingResult is a child of Codec it really shouldn't have CascadeType.ALL on that relation - apart from being able to insert new Codec when saving EncodingResult, you're also specifying that REMOVE operation will be cascaded from EncodingResult to Codec.
I have a query that would be fairly simple using SQL databases. However, in MongoDB, I'm not sure on how to do it. I am building a notification system. I have a collection that stores notifications, and another that creates a document when the user has seen the notification, as such:
notifications collection:
_id: 1
content: "some content"
targetGroup: "somegroup"
seen-notification collection
_id: 1
notificationId: 1
userName: "johndoe"
I'm building an endpoint that should return all notifications in a specific group, that the user has not already seen. In a SQL pseudo-code, I'm trying to do:
SELECT
*
FROM
notifications AS n
INNER JOIN
seen-notification AS sn
ON n._id = sn.notificationId
WHERE
sn.notificationId IS NULL
AND n.targetGroup = "somegroup"
So, basically, a right join with a where clause. I'm using spring data with a MongoRepository. Right now, i'm making two different selects and iterating through them to remove the already seen. I've looked into mongo's $lookup and several SO questions regarding it, but I just couldn't get it work with spring data and MongoRepository. I'm fairly new to mongo with spring data, so maybe there's a way to accomplish this with $lookup and I just didn't figure out how.
try
https://mongoplayground.net/p/-dZHmP9yj-c
expects collection names of notifications and seenNotifications
#Aggregation(pipeline = {
"{$match: { targetGroup: ?0 } }",
"{$lookup: { from: \"seenNotifications\", localField: \"_id\", foreignField: \"notificationId\", as: \"seenNotificationsDocs\"}}",
"{$match: { seenNotificationsDocs: { $size: 0 } } }",
"{$project: { seenNotificationsDocs: 0 } }",
})
List<Notifications> findUnseenNotificationByTargetGroup(String targetGroup);
#Document("notifications")
public class Notifications {
#Id
private Integer id;
private String content;
private String targetGroup;
//getter & setter
}
I'm on a Java Spring Boot project that makes API requests using RestTemplates.
Trying to implement pagination, makes the new JsonArray incoming has as first element an Integer and the rest are JsonElements.
Without pagination the value of the json incoming is:
[
{
"id":1234,
"name": null,
"...":...
},...
]
And these objects with their getter and setters correctly implented. p.d I don't need all the attributes of the incoming jsonelement.
public class WorkListBean extends WorkBean{
private List<WorkBean> lsWorkBean;
}
public class WorkBean implements Serializable {
private static final long serialVersionUID = -...L;
private long id;
private String name;
}
This returns WorkListBean[] to the client and works pretty well.
But with pagination implemented the incoming json is like this:
[
1,
{
"id":1234,
"name": null
},...
]
How could I extract this first Integer of the array?
I think maybe I could use a Deserializer with a Object Mapper to make a custom object that owns a Integer and a list or array of WorkBean. But I'm a little bit lost with this terms. Can anyone confirm am pointing in a good view?
Thanks in advance,
Grettings LaGallinaturuleta
That's a badly designed schema. Allthough it's technically allowed, putting elements of different types in the same array will make every client suffer.
If you can change the server side you're talking to, they should use a more user-friendly schema, like this:
{
"pageNumber":0,
"size":5,
"totalPages":4,
"content":[
{"studentId":"1","name":"Bryan","gender":"Male","age":20},
{"studentId":"2","name":"Ben","gender":"Male","age":22},
{"studentId":"3","name":"Lisa","gender":"Female","age":24},
{"studentId":"4","name":"Sarah","gender":"Female","age":26},
{"studentId":"5","name":"Jay","gender":"Male","age":20}
],
}
If not, you're going to need a custom deserializer indeed.
Is there anyway to insert a new record into a PostgreSQL database with Jooq straight from a POJO which extends a general identity class that has an id field without including the id in the insert statement?
An example POJO:
#Data
public abstract class PersistenceIdentity {
#Id
#Column(name = "id", unique = true, nullable = false, precision = 7, insertable = false)
private Integer id;
#Column(name = "created_date")
private LocalDateTime createdDate;
public abstract Table<R> getJooqTable();
}
#Data
public class SocialNetwork extends PersistenceIdentity {
#Column(name = "name")
private String name;
#Override
public Table<SocialNetworkRecord> getJooqTable() {
return Tables.SOCIAL_NETWORK;
}
}
The PostgreSQL schema is:
CREATE TABLE "social_network" (
id SERIAL NOT NULL PRIMARY KEY,
created_date TIMESTAMP DEFAULT now(),
name TEXT NOT NULL
);
My code to persist the POJO:
public <T extends PersistenceIdentity> T insertRecord(T record) {
Record newRecord = db.newRecord(record.getJooqTable(), record);
if (newRecord instanceof UpdatableRecord) {
((UpdatableRecord) newRecord).store();
}
return newRecord.into(record);
}
I realize I'm probably doing what Jooq really wasn't meant for (i.e. using generic types), however that (appears) to work just fine.
The problem is, Jooq includes the id in the insert statement and I then, of course, get a null value constraint. I don't want it inserted when it's a new record, however I do want it included when it returns the record (after inserting), when updating and also in select statements.
I can't simply exclude the id because I need it later on to easily get around some of the #OneToMany / #ManyToOne limitations.
And I would rather not have to insert the specific values for each POJO (that's why we annotated with #Column).
Does Jooq not honor the #Id or the insertable = false parameter in #Column?
Can anyone shed some light on this?
EDIT 1
Per request, below is the relevant snippet from the jOOQ generated table object. I'm not sure if this is correct or not for what I'm trying to do (i.e. allow the database to generate the ID), but I would think nextval('social_network_id_seq'::regclass) would accomplish that.
#Generated(
value = {
"http://www.jooq.org",
"jOOQ version:3.9.1"
},
comments = "This class is generated by jOOQ"
)
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class SocialNetwork extends TableImpl<SocialNetworkRecord> {
/**
* The column <code>public.social_network.id</code>.
*/
public final TableField<SocialNetworkRecord, Integer> ID = createField("id", org.jooq.impl.SQLDataType.INTEGER.defaultValue(org.jooq.impl.DSL.field("nextval('social_network_id_seq'::regclass)", org.jooq.impl.SQLDataType.INTEGER)), this, "");
}
Also, we use the mvn jooq-codegen:generate -Djooq.generator.name=org.jooq.util.XMLGenerator to generate the XML schema and then generate the the jOOQ table objects from that XML config. The thinking is we can push the XML config to github and all builds can simply regenerate the table objects from that.
Here is the XML:
<column>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
<column_name>id</column_name>
<data_type>integer</data_type>
<character_maximum_length>0</character_maximum_length>
<numeric_precision>32</numeric_precision>
<numeric_scale>0</numeric_scale>
<ordinal_position>1</ordinal_position>
<column_default>nextval('social_network_id_seq'::regclass)</column_default>
</column>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>social_network_pkey</constraint_name>
<constraint_type>PRIMARY KEY</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>2200_17431_1_not_null</constraint_name>
<constraint_type>CHECK</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>2200_17431_3_not_null</constraint_name>
<constraint_type>CHECK</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<key_column_usage>
<column_name>id</column_name>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>social_network_pkey</constraint_name>
<ordinal_position>0</ordinal_position>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</key_column_usage>
EDIT 2
My SocialNetwork jOOQ-generated table object does not have a getIdentity() method, however it does have a getPrimaryKey() method and if it helps, my SocialNetworkRecord class has two Constructors:
public SocialNetworkRecord() {
super(SocialNetwork.SOCIAL_NETWORK);
}
/**
* Create a detached, initialised SocialNetworkRecord
*/
public SocialNetworkRecord(Integer id, Timestamp createdDate, String name) {
super(SocialNetwork.SOCIAL_NETWORK);
set(0, id);
set(1, createdDate);
set(2, name);
}
The way jOOQ works, there are two elements worth explaining:
Step 1: Record.from(Object):
Record newRecord = db.newRecord(record.getJooqTable(), record);
This call is convenience for this:
Record newRecord = db.newRecord(record.getJooqTable());
newRecord.from(record);
And the Record.from(Object) will copy all values from the record to the newRecord by using Record.set(Field, Object), which again sets the record's internal Record.changed(Field) flag.
Step 2: UpdatableRecord.store()
Your call to:
((UpdatableRecord) newRecord).store();
Will take all changed() fields into consideration for the relevant INSERT or UPDATE statement that is executed. The rationale here is that people sometimes want to set the primary key value explicitly, and not let an identity generate the value for them. Even if an identity is present on the primary key, it may sometimes be desireable to override its value. SQL standard databases (e.g. Oracle 12c) thus support two ways of specifying an identity:
-- This can be overridden
GENERATED BY DEFAULT AS IDENTITY
-- This can never be overridden
GENERATED ALWAYS AS IDENTITY
(MySQL's AUTO_INCREMENT or PostgreSQL's SERIAL type work the same way)
jOOQ assumes GENERATED BY DEFAULT AS IDENTITY here. The only exception to the above behaviour is when the identity column is NOT NULL and the Record value for the identity is null and jOOQ's meta model is aware of both:
- `NOT NULL` constraint
- `GENERATED BY DEFAULT AS IDENTITY`
Then, jOOQ will omit considering the identity value for insertion / update.
Bug in 3.9.2 and less:
Note that up until jOOQ version 3.9.2, there was a bug / missing feature in the XMLGenerator that produces the XML file you're importing: https://github.com/jOOQ/jOOQ/issues/6141. This bug resulted in no identity information being generated.
Workaround 1: If you cannot influence the jOOQ meta model
If, for some reason, you cannot get the jOOQ meta model to reflect your NOT NULL constraint and your DEFAULT clause, you could work around this limitation by resetting the value of the identity right after your Record.from(Object) call using Record.reset(Field):
Record newRecord = db.newRecord(record.getJooqTable(), record);
newRecord.reset(identityColumn);
((UpdatableRecord) newRecord).store();
Workaround 2: Generate a synthetic identity
The code generator has a feature to generate synthetic identities. For instance, if all your identity columns are called ID, you could write this:
<!-- fully qualified -->
<syntheticIdentities>.*?\.ID</syntheticIdentities>
Or this:
<!-- unqualified -->
<syntheticIdentities>ID</syntheticIdentities>
I'm trying to follow the JPA tutorial and using ElementCollection to record employee phone numbers:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Short version
What I need is a class like this:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#Embedded
List<Phone> phones;
}
that stores each person's phone numbers in a collection.
Long version
I follow the tutorial code:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#ElementCollection
#CollectionTable(
name="Phones",
joinColumns=#JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type")
String type = "";
#Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
with a slight difference that I only keep one table. I tried to use the following code to add records to this table:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
but it keeps throwing exceptions
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "type" violates not-null constraint Detail: Failing
row contains (0, null, null). Error Code: 0 Call: INSERT INTO Phones
(owner_id) VALUES (?) bind => [1 parameter bound] Query:
InsertObjectQuery(tutorial.Phone1#162e295)
What did I do wrong?
Sadly, i think the slight difference that you only keep one table is the problem here.
Look at the declaration of the PhoneId class (which i would suggest is better called PhoneOwner or something like that):
#Entity
#Table(name="Phones")
public class PhoneId {
When you declare that a class is an entity mapped to a certain table, you are making a set of assertions, of which two are particularly important here. Firstly, that there is one row in the table for each instance of the entity, and vice versa. Secondly, that there is one column in the table for each scalar field of the entity, and vice versa. Both of these are at the heart of the idea of object-relational mapping.
However, in your schema, neither of these assertions hold. In the data you gave:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
There are two rows corresponding to the entity with owner_id 1, violating the first assertion. There are columns TYPE and NUMBER which are not mapped to fields in the entity, violating the second assertion.
(To be clear, there is nothing wrong with your declaration of the Phone class or the phones field - just the PhoneId entity)
As a result, when your JPA provider tries to insert an instance of PhoneId into the database, it runs into trouble. Because there are no mappings for the TYPE and NUMBER columns in PhoneId, when it generates the SQL for the insert, it does not include values for them. This is why you get the error you see - the provider writes INSERT INTO Phones (owner_id) VALUES (?), which PostgreSQL treats as INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null), which is rejected.
Even if you did manage to insert a row into this table, you would then run into trouble on retrieving an object from it. Say you asked for the instance of PhoneId with owner_id 1. The provider would write SQL amounting to select * from Phones where owner_id = 1, and it would expect that to find exactly one row, which it can map to an object. But it will find two rows!
The solution, i'm afraid, is to use two tables, one for PhoneId, and one for Phone. The table for PhoneId will be trivially simple, but it is necessary for the correct operation of the JPA machinery.
Assuming you rename PhoneId to PhoneOwner, the tables need to look like:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(I've made (owner_id, number) the primary key for Phone, on the assumption that one owner might have more than one number of a given type, but will never have one number recorded under two types. You might prefer (owner_id, type) if that better reflects your domain.)
The entities are then:
#Entity
#Table(name="PhoneOwner")
public class PhoneOwner {
#Id
#Column(name="owner_id")
long id;
#ElementCollection
#CollectionTable(name = "Phone", joinColumns = #JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type", nullable = false)
String type;
#Column(name="number", nullable = false)
String number;
}
Now, if you really don't want to introduce a table for the PhoneOwner, then you might be able to get out of it using a view. Like this:
create view PhoneOwner as select distinct owner_id from Phone;
As far as the JPA provider can tell, this is a table, and it will support the queries it needs to do to read data.
However, it won't support inserts. If you ever needed to add a phone for an owner who is not currently in the database, you would need to go round the back and insert a row directly into Phone. Not very nice.