Hibernate SQLQuery with transient properties - java

I need to make an hibernate SQLQuery with db2 and this query is returning me some fields which are calculated and have no relation with any columns in database.
The goal is setting the values of these sum() calculations from SQLQuery on three new transient fields in a Java Object which already existed.
The SQLQuery uses the syntax:
SELECT id as {entityObject.id},
name as {entityObject.name},
order as {entityObject.order},
SUM(CASE
WHEN pv.value_id = 1
AND pv.value=1 THEN 1 ELSE 0 END) AS {entityObject.someCount}
The problem is that Hibernate complains and says to need a column for someCount. It seems not to help declaring the java field as transient or even using the #Transient annotation from javax.persistence at the same time.
If I only declare in the hbm.xml mapping file:
<property name="id" type="java.lang.Integer" column="someColumn" />
<!-- Some more fields here -->
<!-- THE IMPORTANT ONE -->
<property name="someCount" type="java.lang.Integer"/>
Java Object:
public class EntityObject implements Serializable {
private static final long serialVersionUID = 1479579608940145961L;
private Integer id;
private String name;
private Integer order;
// This is the one giving me hell. I've tried with #Transient also
private transient Integer someCount;
public Category() {
}
public Category(final String name, final Integer order) {
this.name = name;
this.order = order;
}
public Integer getOrder() {
return this.order;
}
public void setOrder(final Integer order) {
this.order = order;
}
public Integer getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public Integer getSomeCount() {
return someCount;
}
public void setSomeCount(final Integer count) {
this.someCount = count;
}
}
It asks me for a column, and I have tried inserting a fake column and it does not work. The thing is that I want these 'count' fields only to be set from the SQLQuery and to be empty and null when coming from a regular Hibernate Query.
I have looked at the docs and googled, and it seems that you can declare a field transient by only not declaring it at the hibernate mapping file, but then it does not set it on the object with the "as {entityObject.someCount}" even when I have getters/setters declared.
Help please.
Thanks very much in advance.

The only option available that might do all this directly from the Database without having to issue additional queries is a Hibernate Formula property:
http://wiki.jrapid.com/w/Formula_(attribute_of_property)
<property name="someCount" formula="select count(*) from some_table where table_key = ?"/>
The ? placeholder will be populated automatically with the ID of the current instance.

1 Create a POJO:
public class SumValue{
private BigInteger myId;
private String myName;
private BigInteger myOrder;
private BigInteger mySum;
....
getters and setters here
....
}
2 Minor changes in your query
SELECT id as "myId",
name as "myName",
order as "myOrder",
SUM(CASE
WHEN pv.value_id = 1
AND pv.value=1 THEN 1 ELSE 0 END) AS "mySum"
3 Execute native sql
List<SumValue> jobStateViewList = (List<SumValue>)getSessionFactory().getCurrentSession()
.createSQLQuery(yourQuery)
.setResultTransformer(
new AliasToBeanResultTransformer(SumValue.class)
).list();

Related

Dropwizard JDBI 3 ResultSetMapper ignore field

I have this Pojo:
private long id;
#NotEmpty
#JsonProperty("name")
private String name;
#NotEmpty
#JsonProperty("id")
private String tagUuid;
#NotEmpty
#JsonProperty("archived")
private boolean archived;
#NotEmpty
#JsonProperty("creationDate")
private DateTime creationDate;
private Integer count;
#JsonCreator
public Tag() {
}
public Tag(long id, String tagUuid, String name, boolean archived, Timestamp creationDate, Integer count) {
this.id = id;
this.tagUuid = tagUuid;
this.name = name;
this.archived = archived;
this.creationDate = new DateTime(creationDate);
this.count = count;
}
This is my result set mapper:
public class TagMapper implements ResultSetMapper<Tag> {
#Override
public Tag map(int index, ResultSet r, StatementContext ctx) throws SQLException {
return new Tag(
r.getLong("id"),
r.getString("tag_uuid"),
r.getString("name"),
r.getBoolean("archived"),
r.getTimestamp("creation_date"),
r.getInt("count")
);
}
}
How can I fetch from the database one column less. For example in some queries I fetch only tagUuid and name and not the other fields.
But if I do this I get this exception: org.skife.jdbi.v2.exceptions.ResultSetException: Exception thrown while attempting to traverse the result set. I tried to create a addtional Tag Constructor without the other parameters.
This is the query I try to run:
#SqlQuery("SELECT t.id, t.tag_uuid as tag_uuid, t.name, t.archived, t.creation_date FROM tags t WHERE t.tag_uuid = :tag_uuid LIMIT 1")
public Tag fetchTagByUuid(#Bind("tag_uuid") String tagUuid);
You can just return the extra column in your query SQL.
#SqlQuery("SELECT t.id, t.tag_uuid as tag_uuid, t.name, t.archived, " +
"t.creation_date, 0 AS count FROM tags t " +
"WHERE t.tag_uuid = :tag_uuid LIMIT 1")
public Tag fetchTagByUuid(#Bind("tag_uuid") String tagUuid);
You can retrieve the values whatever you want and before passing the values to Tag constructor check their existence in the ResultSet. If the attribute is not present then you can pass the default value for the attributes.
You can check the value as r.getString("tag_uuid") != null (for strings)
then tag_uuid = r.getString("tag_uuid")

mongo db throws error trying to edit _id field in update

I am trying to update a document
final Update update = new Update();
update.set("id", String.valueOf(partnerId));
update.pushAll("appIds", appIds.toArray());
mongoTemplate.upsert(Query.query(Criteria.where("partnerId").is(partnerId)), update, PartnerAppIds.class);
On executing this getting this error
{ "err" : "After applying the update to the document {_id: \"66\" , ...}, the (immutable) field '_id' was found to have been altered to _id: \"5}
The document looks like this
public class PartnerAppIds {
#Indexed
private String id;
#Indexed
private Long partnerId;
#Indexed
private Set<String> appIds;
public Long getPartnerId() {
return partnerId;
}
public void setPartnerId(Long partnerId) {
this.partnerId = partnerId;
}
public Set<String> getAppIds() {
return appIds;
}
public void setAppIds(Set<String> appIds) {
this.appIds = appIds;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
My question is as I have a id field, why mongo is assuming i am updating '_id' field, where as I am updating the id field here. Are _id and id same for mongo here
From the docs,
A property or field without an annotation but named id will be mapped
to the _id field.
When querying and updating MongoTemplate will use the converter to
handle conversions of the Query and Update objects that correspond to
the above rules for saving documents so field names and types used in
your queries will be able to match what is in your domain classes.
So use field with different name other than _id or id if you need to update.

JPA: Delete orphans, invalid column

Recently I have asked a very similar question on Stack overflow which turned out to be a duplicate of another question. In that other question there was a workaround which I applied and solved my problem. Now, this time the workaround doesn't work, and all other mentioned solutions don't work. Also all the solutions from other threads linked to the first thread don't work.
This was my question at first:
SQLServerException: Invalid column name
And this was the duplication:
hibernate column name issues
I have checked the topics on the right in the Linked and Related sections but can't find an solution to my problem. I also cannot comprehend the reason why my problem occurs.
I have 2 tables: Declaration and File (I won't mention my other tables here because they are irrelevant to the problem)
CREATE TABLE [dbo].[Declaration] (
[number] INT NOT NULL,
[status] VARCHAR (50) NOT NULL,
[name] VARCHAR (50) NOT NULL,
[description] VARCHAR (250) NOT NULL,
[amount] FLOAT (53) NOT NULL,
[date] DATE NOT NULL,
[period_id] INT NOT NULL,
[client_project_id] INT NOT NULL,
PRIMARY KEY CLUSTERED ([number] ASC),
CONSTRAINT [fk_client_period] FOREIGN KEY ([client_project_id]) REFERENCES [dbo].[ClientProject] ([number]),
CONSTRAINT [fk_period] FOREIGN KEY ([period_id]) REFERENCES [dbo].[Period] ([number])
);
CREATE TABLE [dbo].[File] (
[number] INT NOT NULL,
[path] VARCHAR (50) NOT NULL,
[declaration_id] INT NOT NULL,
PRIMARY KEY CLUSTERED ([number] ASC),
CONSTRAINT [fk_file] FOREIGN KEY ([declaration_id]) REFERENCES [dbo].[Declaration] ([number])
);
With the corresponding classes:
#Entity
#Table(name = "[file]")
public class File {
#Id
private int number;
private String path;
#ManyToOne(targetEntity = Declaration.class)
private int declaration_id;
public int getDeclaration_id() {
return declaration_id;
}
public void setDeclaration_id(int declaration_id) {
this.declaration_id = declaration_id;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
And
#Entity
public class Declaration {
#Id
private int number;
private String status;
private String name;
private String description;
private double amount;
private Date date;
private int period_id;
private int client_project_id;
#OneToMany(targetEntity = File.class,mappedBy = "declaration_id",orphanRemoval = true)
private List<File> files = new ArrayList<>();
public List<File> getFiles() {
return files;
}
public void setFiles(List<File> files) {
this.files = files;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number= number;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public int getPeriod_id() {
return period_id;
}
public void setPeriod_id(int period_id) {
this.period_id = period_id;
}
public int getClient_project_id() {
return client_project_id;
}
public void setClient_project_id(int client_project_id) {
this.client_project_id = client_project_id;
}
}
I have defined my #ManyToOne and #OneToMany relations based on these topics and tutorials:
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
JPA JoinColumn vs mappedBy
What I want: Delete Declaration, automatically delete files related to the declaration
What I get: Invalid column name 'declaration_id_number'.
What I have tried:
- renaming fields in database to declaration_id_number (results in declaration_id_number_number)
- using #Column(name="declaration_id") on declaration_id field
- using #Colum(name="declaration_id") on the getter field
- using #JoinColumn(name="fk_file") on the declaration_id field
- Using different kinds of naming stategies (in application.properties), including the default one
spring.jpa.hibernate.naming.strategy: org.hibernate.cfg.EJB3NamingStrategy
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
The actual SQL query:
select files0_.declaration_id_number as declarat3_3_0_, files0_.number as number1_3_0_, files0_.number as number1_3_1_, files0_.declaration_id_number as declarat3_3_1_, files0_.path as path2_3_1_ from [file] files0_ where files0_.declaration_id_number=?
select declaratio0_.number as number1_2_0_, declaratio0_.amount as amount2_2_0_, declaratio0_.client_project_id as client_p3_2_0_, declaratio0_.date as date4_2_0_, declaratio0_.description as descript5_2_0_, declaratio0_.name as name6_2_0_, declaratio0_.period_id as period_i7_2_0_, declaratio0_.status as status8_2_0_ from declaration declaratio0_ where declaratio0_.number=?
I am running Spring boot with JPA Hibernate 5.2.10
Is there anyone out there who knows why this happends, if I know why it happends I might be able to fix the problem my self. Right now I am completely stuck.
Thanks in advance.
EDIT:
Ok, so by accident I solved my own problem, I still don't know why the problem occured in the first place. According to this answer(s) of this topic:
JPA JoinColumn vs mappedBy
You use #ManyToOne & #OnyToMany
In my case I don't need to use #ManyToOne in the File class. I only need #OneToMany in my Declaration class. No more errors occur after I removed this annotation.
If anyone knows the reason for this problem, please provide an answer so that it can be of use in the future for me or someone else.
In my case I don't need to use #ManyToOne in the File class. I only need #OneToMany in my Declaration class. No more errors occur after I removed this annotation.
I don't think that this will work. If you remove the #ManyToOne annotation, the persistence provider will create a join table by default to maintain the relationship. What you mean is probably that you don't get any exception. But look at the database schema:
CREATE TABLE [dbo].[File] (
[number] INT NOT NULL,
[path] VARCHAR (50) NOT NULL,
[declaration_id] INT NOT NULL,
PRIMARY KEY CLUSTERED ([number] ASC),
CONSTRAINT [fk_file] FOREIGN KEY ([declaration_id]) REFERENCES [dbo].[Declaration] ([number])
);
declaration_id is declaraed to be NOT NULL which means you cannot save anything in this table unless you assign it an entry in the Declaration table.
You have defined a foreign key constraint which means your database will check this when you save a file record.
This means that you have two options:
you need an #ManyToOne annotation so that JPA can map the entities correctly and automatically that will correspond to your database schema, or
you remove the foreign key field declaration_id and the corresponding referential integrity constraint from the File table. In this case, the persistence provider will create a join table by default for you, unless you customize it.
So if you want to use the first option, i.e. #ManyToOne annotation, you have to map the entities as follows:
#Entity
#Table(name = "[file]")
public class File {
#Id
private int number;
private String path;
#ManyToOne
#JoinColumn(name = "declaration_id")
private Declaration declaration;
public int getDeclaration_id() {
return declaration_id;
}
// ... getters and setters
}
and a slightly modified Declaration entity:
#Entity
public class Declaration {
#Id
private int number;
// ... other fields
#OneToMany(mappedBy = "declaration",orphanRemoval = true)
private List<File> files = new ArrayList<>();
// ... Rest of the code
}
Notes:
I removed targetEntity = File.class attribute from the annotation because you don't need it as your collection already implies the type.
Why are you putting table/column names into the square brackets? They make the code unreadable and I don't see the benefit of using it.

"IllegalArgumentException occurred while calling setter for property" from sqlserver numeric to java object

I already have such an error, when you try to perform unapropriate matching (date to boolean, and so) which I had been able to fix quite easilly.
But this time, I am quite confused, because hibernate refuses to match a "numeric" Id to a Java "Long" (and it also failed when setter is made for Double, Integer, Float, String, int, long, etc.)
The sql-server field "id" is a NUMERIC(19,0)
My DTO is :
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
public class DtoResult {
private Long id;
private String name;
// ...
public Long getId() {
return id;
}
public void setId(final Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
My hibernate query :
final SQLQuery query= getCurrentSession().createSQLQuery(select + from + where);
query.setParameter("manyFields", manyFields);
query
.addScalar("id")
.addScalar("name")
.setResultTransformer(Transformers.aliasToBean(DtoResult.class));
return query.list(); // List<DtoResult>
Error:
IllegalArgumentException occurred while calling setter for property [com.some.thing.DtoResult.id (expected type = java.lang.Long)]; target = [com.some.thing.DtoResult#77a70b79], property value = [269895]
I am really puzzled about this, thus any help is welcome.
Thanks for reading untill there.
Just add the expected type, like:
.addScalar("id", new LongType())
.addScalar("name", new StringType())
The number from database query is not Long but BigInteger.
Change setter to:
public void setId(final Number id) {
this.id = id != null ? id.longValue() : null;
}

Object Oriented Design for generic hibernate object

I'm currently working good object oriented principles, and hibernate, I have this POJO, in which the properties would be dynamically populated. This is a design pattern I've read for good Object Oriented Design, in which it would be easy to add attributes to specificic object without breaking the application. My question is, how can you map this to a table, when your attributes is supposedly dynamic, I'm using an enum to limit the key value pairs for the map, but ideally it can still grow. I am only using in-memory database (h2) and I'm not going to be using the code for production use. This is for learning purposes only. see code below:
public class Transaction {
private static Map<Object, Object> properties;
public Transaction(){
if(null != properties)
properties = new LinkedHashMap<Object, Object>();
}
public Transaction(Map<Object, Object> properties){
if(null != properties)
setProperties(properties);
}
public void setProperties(Map<Object, Object> prop){
properties = prop;
}
public void setProperties(Properties property, String value){
properties.put(property, value);
}
public Map<Object, Object> getProperties(){
return properties;
}
public String getProperties(Properties property){
return (String) properties.get(property);
}
}
So I want to be able to create a table that would have this properties, dynamically,
My Enum:
public enum Properties {
Entry("Entry"), Id("Entry_ID"), Name("Name"), Credit("Credit");
private final String description;
private Properties(final String description){
this.description = description;
}
#Override
public String toString(){
return description;
}
}
I have this hibernate mapping, but as you can see this would be need to be updated everytime a field is updated, I need a generic mapping so that when I change/add the attributes, annotation or xml would be okay, see below:
<class name="Transaction" table="TRANSACTION">
<id name="id" column="ENTRY_ID">
<generator class="increment"/>
</id>
<property name="name"/>
<property name="credit" type="boolean" column="IS_CREDIT"/>
</class>
UserDefinedField on Marin Fowler's web site may be a perfect starting point for exploring general answers to this question.
As for Hibernate: It's really designed for statically binding tables to objects and you may have significant problems if you change the schema while running. You can implement the following solutions, though:
Serialized LOB (you serialize your Map into a binary field or - using JSON/XML - a text field). This is a half-and-half approach - half tabular/normal form/SQL and half not-SQL. So, if this approach is attractive, you might want to consider going all-in with a NoSQL database as discussed later
Attribute table, where your customized attributes are stored in a key-value pair table that joins to to the master table. This can be mapped in Hibernate using Indexed Collections (see section 7.2.2.2 Maps) and you would end up with something quite like in your question:
#Entity
public class Transaction {
#Id #GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
// ... snip ...
#OneToMany(mappedBy="transaction")
#MapKey(name="name")
public Map<String, String> getProperties(){
return properties;
}
public void setProperties(Map<String, String> prop){
properties = prop;
}
private Map<String, String> properties; // NB: Type has to be <String, String> because the column name is a String and you have defined the property value to be a String.
public void setProperty(Properties property, String value){
properties.put(property, value);
}
public String getProperty(String name){
return (String) properties.get(property);
}
}
#Entity
public class Property {
#Id #GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
#ManyToOne
public Transaction getTransaction() { return transaction; }
public void setTransaction(Transaction transaction) { this.transaction = transaction; }
private Transaction transaction;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
private String name;
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
private String description;
}
Pre-defined custom-fields, where you start with a really wide table with loads of unused columns. In this implementation you end up defining a mapping between your arbitrary property names and the pre-defined column names (getString1(), getString10(), etc)
However, a much better solution for you may be to use a NoSQL database - specifically a document-based one. These allow you to store and retrieve arbitrary data-structures (maps and lists). Interestingly, using such an approach makes binding to the data store significantly easier.
MongoDB or Redis (Java bindings at Jedis) are examples.

Categories

Resources