In our database table, we record a large string and its corresponding md5 value. In mysql5, we insert such a record with
insert (md5,content) values (md5(content), hex(content));
Moving to hibernate, I have annotated the entity
#Column(name = "content", columnDefinition = "MEDIUMTEXT")
#ColumnTransformer(read = "unhex(content)", write="hex(?)")
private String content;
which works great. But I don't see how to annotate the md5 column so that it can be automatically generated on insert. In particular, a columntransformer won't work, since the ? in the annotation refers to the md5 field, not the content field.
Any observations, or help appreciated.
You can use hibernate interceptors and exactly pre-save (in this case) events for encryption.
read about hibernate interceptors here Hibernate Interceptors
Related
I'm trying to lazily fetch single byte[] content java property using Hibernate under Spring Boot, accessing PostgreSQL database. So I pulled together testing app for testing different solutions. One of them required me to use #Lob annotation on said property, so I did. Now reading entity from the database leads to very curious error, precisely:
Bad value for type long : \x454545454545445455
The value \x45... is value of bytea column not the bigint one, why is it trying to force it into the long even though it's wrong column? Why annotation on one column somehow affects another one?
As for fix, removing #Lob seems to work (at least in my stack) but the problem remains unexplained to me and I would like to know what is going rather than just blindly moving on. Is it bug or I am misunderstanding something completely?
Entity:
#Entity
#Table(name = "blobentity")
public class BlobEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Lob //this annotation breaks code
#Column(name = "content")
#Basic(fetch = FetchType.LAZY)
private byte[] content;
#Column(name = "name")
private String name;
//getters/setters
}
Repository:
#Repository
public interface BlobRepo extends JpaRepository<BlobEntity, Long> {
}
Calling code:
#Autowired
BlobRepo blobrepo;
#GetMapping("lazyBlob")
public String blob () {
var t = blobrepo.findAll().get(0);
var name = t.getName();
var dataAccessedIfLazy = t.getContent();
return t.getName();
}
Postgres DDL:
CREATE TABLE test.blobentity (
id bigserial NOT NULL DEFAULT nextval('test.blobentity_id_seq'::regclass),
"name" varchar NULL,
"content" bytea NULL,
CONSTRAINT blobentity_pk PRIMARY KEY (id)
);
Select result:
Used version:
PostgreSQL 10.4; springframework.boot 2.4.2; hibernate version that comes with this spring boot version
The bytea type is inlined into the table whereas other types are chunked into a separate table which is called TOAST on PostgreSQL. To access these values, database have a concept often referred to as a LOB locator which essentially is just an id for doing the lookup. Some drivers/databases just work either way but others might need to match the actual physical representation. In your case, using #Lob is just wrong because AFAIK bytea is inlined up to a certain size and de-TOASTed i.e. materialized automatically behind the scenes if necessary. If you were using the varbinary/blob type or something like that, you would have to use #Lob as in that case, the main table only contains this LOB locator which is a long. The driver then knows when you ask for the value by using getBlob that it has to execute some select get_lob(?) query to retrieve the actual contents.
I'm currently using QueryDSL and I must add a WHERE clause to filter by a field which stores a huge amount of information (readable text) and it's created as a LOB field.
This is the field in my entity:
#Lob
#Column(name = "MY_FIELD", nullable = true)
private byte[] myField;
which is generated in this way in my "Q" class:
public final ArrayPath<byte[], Byte> myField = createArray("myField", byte[].class);
I can recover the information in this field without a problem. However, when I was trying to add the filtering clause, I realized the ArrayPath object doesn't have a like method, so I tried to do it in a different way.
I tried different approaches and I came up to this:
Expressions.predicate(Ops.LIKE, Expressions.stringPath("MY_FIELD"), Expressions.constant(stringValue));
The SQL code generated with the previous predicate is the following:
...
WHERE
MY_FIELD like '%?%' escape '!'
...
If I try to execute this SQL command directly in my database it perfectly works, it recovers the correct rows depending on the "?" param. However, my application doesn't recover any of them even though it's executing the very same SQL command.
Is there anything I'm missing? Could it be done in a different way?
Thank you very much in advance.
PS: I'm using SQL Server 2011.
By default a byte[] is mapped to an Array path. In case of a (C)LOB, you want to map it as String path instead. You can hint the code generator by specifying the QueryType:
#Lob
#QueryType(PropertyType.STRING)
#Column(name = "MY_FIELD", nullable = true)
private byte[] myField;
However, #Column(name = "MY_FIELD", nullable = true) seems to imply that you're querying JPA instead of plain SQL. Be aware that some JPA vendors may not support the like function for CLOBs.
I am facing a very strange issue at the moment.
I have an entity that contains a property that is an element collection.
#ElementCollection(targetClass=Integer.class, fetch = FetchType.EAGER)
#CollectionTable(name="campaign_publisher", joinColumns=#JoinColumn(name="campaign_id"))
#Column(name = "publisher_id")
...
#NotEmpty(message = "campaign.publishers.missing")
public Set<Integer> getPublishers() {
return this.publishers;
}
public Campaign setPublishers(Set<Integer> publisherId) {
this.publishers = publisherId;
return this;
}
This all works fine. The values are validated and saved correct.
I also want this entity to have optimistic concurrency so I applied a #Version annotation as well.
#Version
private Long etag = 0L;
...
public Long getEtag() {
return etag;
}
public void setEtag(Long etag) {
this.etag = etag;
}
By adding the #Version annotation the #NotEmpty validation on my set of publishers always returns invalid.
To try and diagnose this I have tried the following:
Creating a custom validator at the entity level so I can inspect the values in the entity. I found that the Set of values have been replaced with an empty PersistentSet which is causing the validation to always fail.
I created some unit tests for the entity that uses a validator that is retrieved from the validationfactory and this validator seems to work as expected.
I have also tried to change the ElementCollection to a many-to-many relationship and a bi-directional one-to-many but the issue persists.
Right now I am out of ideas. The only thing I have found that works correctly is disabling the hibernate validation and manually calling the validator just before I save my data.
So my questions are:
Has anyone encountered this issue before?
Any advice on what I could try next?
Thank you all for reading!
Short answer: Set the initial value for etag = null.
// this should do the trick
#Version
private Long etag = null;
Longer one : When you are adding a optimistic locking via adding #Version annotation on a field with a default value you are making hibernate/spring-data think that the entity is not a new one (even the id is null). So on initial save instead of persisting entity undelying libraries try to do a merge. And merging transient entity forces hibernate to just one by one copy all the properties from source entity (the ones which you are persisting) to the target one (which is autocreate by hibernate with all the properties set to default values aka nulls) and here comes the problem, as hibernate will just copy the values of associations of FROM_PARENT type or in other words only associations which are hold on entity side but in your case the association is TO_PARENT (a foreign key from child to parent) hibernate will try to postpone association persistance after main entity save, but save will not work as entity will not pass #NotEmpty validation.
First I would suggest to remove the default value initialization for your #Version property. This property is maintained by hibernate, and should be initialized by it.
Second: are you sure that you are validating the fully constructed entity? i.e. you are constructing something, then do something, and for exact persist/flush cycle your entity is in wrong condition.
To clarify this, while you are on a Spring side, I would suggest to introduce service-level validation on your DAO layer. I.e. force the bean validation during initial call to DAO, rather then bean validation of entity during flush (yeap hibernate batches lots of things, and exact validation happens only during flush cycle).
To achieve this: mark your DAO #Validated and make your function arguments beign validated: FancyEntity store(#Valid #NotNull FancyEntity fancyEntity) { fancyEntity = em.persist(fancyEntity); em.flush(); return fancyEntity;}
By making this, you will be sure that you are storing valid entity: the validation would happen before store method is called. This will reveal the place where your entity became invalid: in your service layer, or in bad behaving hibernate layer.
I noticed that you use mixed access: methods and fields. In this case you can try to set #Version on the method:
#Version
public Long getEtag() {
return etag;
}
not on the field.
Background
I'm writing a project using Spring MVC (Framework v4.0.6.RELEASE, JPA v1.6.2.RELEASE) and Hibernate (Core v4.3.6.FINAL, JPA API v2.1). In my project, there are entities called 'Project'. Each of these projects have their unique, auto-generated IDs as primary keys. This ID is generated by the following code:
#Id
#Column(name = "project_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long projectId;
This code works as expected and automatically creates unique IDs.
Problem
Each of these projects are supposed to have a random, unique1 'secret' String, just like those assigned by API providers like Facebook, Twitter, etc. So, to achieve this, I tried using the following code, as per the Hibernate docs:
#Column(name = "project_secret", nullable = false, unique = true)
#GenericGenerator(name = "uuid-gen", strategy = "uuid")
#GeneratedValue(generator = "uuid-gen")
private String projectSecret;
However, whenever I try to create a new project entity, I'm greeted by a org.springframework.dao.DataIntegrityViolationException with root cause:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Column 'project_secret' cannot be null
This should be auto-generated by Hibernate on creation, must be random and unique1. A 128-bit UUID is enough for me (32 characters w/out dashes) and I read that Hibernate has a UUID generator, so that's what I was aiming to use.
Further Info
After searching for hours, I'm no closer to solving it the way I want to do it. I found one possible solution, which is to include:
#PrePersist
private void generateSecret(){
this.setProjectSecret(UUID.randomUUID().toString());
}
in the Project entity class. When this method is inserted (and #GenericGenerator & #GeneratedValue tags removed), the project secret is correctly generated and inserted; system works as expected; no exceptions are thrown. However, (I believe) this can't ensure uniqueness2 and just causes an exception when a duplicate secret is inserted. I want to ensure uniqueness and preferably want to solve this with built-in Hibernate generators.
(Notes)
I actually am not sure if uniqueness should be enforced. I suppose having every secret unique can (theoretically) create an additional layer of security, which takes me to:
I realise that the probability of UUID collision is very very low, so UUID generation ensures that uniqueness in a probabilistic sense but can (or should) I be really sure of it?
I had an issue like this before and I realised after a while that it was my database table that was causing the issue. This might be the same problem you are having...
For your project_id ensure you use the following when you are creating that column in the database
GENERATED ALWAYS AS IDENTITY
I hope this is the same issue and that this will be of help to you. Also would recommend using uuid2 as your strategy.
see here... http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/mapping.html#d0e5294
Edit
After realising that project_secret is not the #id field then the answer is that hibernate does not support generated values on any column except for the #id field.
See here for more details : Hibernate JPA Sequence (non-Id)
If I use JPA (EclipseLink) to create tables a String type results in a varchar2(255). How could I tell JPA (via Annotation) to create a varchar2(20) attribute.
If I have a List JPA creates a BLOB(4000) but I would like a varchar2 (my serialized object's string is short)
How is this possible? Do I have to do it by hand?
You need to use the columnDefinition property of the #Column annotation. i.e.
#Column(columnDefinition="varchar2(20)")
If I use JPA (EclipseLink) to create tables a String type results in a varchar2(255). How could I tell JPA (via Annotation) to create a varchar2(20) attribute.
Using the columnDefinition can break portability from one database to another. For a string-valued column, prefer using the length element (which defaults to 255):
#Column(length=20)
String someString;
You can set the length on your #Column annotation, as such:
#Column(length = 20)
Note that length is only for text columns. For numeric, you can use precision and scale.
#Column(name = "doc_number", columnDefinition = "varchar2(20)")
Please, try that.