7

I want one of the fields to be ignored when called save() method. The field is gonna get populated automatically by the database and returned. It should be treated as a read-only field.

I am concerned about private Timestamp ts; field:

@Entity
@Table(name = "time_series", schema = "ms")
@IdClass(Reading.class)
public class Reading implements Serializable {

   private static final long serialVersionUID = 1L;

   @Id
   @Column(name = "name", nullable = false)
   private String sensorName;

   @Id
   @Column(name = "ts", insertable = false, updatable = false)
   private Timestamp ts;

   @Column(name = "reading")
   private Double value;
   ...

As you see, I use insertable = false, updatable = false are inside the @Column annotation, so I'd expect that ts is ignored when forming the actual SQL behind the curtain.

@Override
@Transactional(readOnly = false)
public Reading save(Reading r) {        
    return readingRepository.save(r);
}

ReadingRepository is basically extended Spring's CrudRepository which has save(...) method.

When I save Reading object with ts=null I get an error from Postgres:

ERROR: null value in column "ts" violates not-null constraint

because Spring Data did not actually ignore the ts field based what I see from the log:

insert into ms.time_series (ts, name, reading) values (NULL, 'sensor1', 10.0)

Clearly, I want the query to be without ts like this:

insert into ms.time_series (name, reading) values ('sensor1', 10.0)

Why is the field not being ignored?

Now if you ask me whether my database schema is okay I say yes. When I type SQL query in console without the ts everything is fine. I even tried @Generated and @GeneratedValue annotations. Name and ts are both forming a primary key for the table, however, the result is the same if I make only one of them a PK or if I add an extra surrogate ID column. Same result...

Am I overlooking something or is there maybe a bug in the Spring framework?? I am using Spring 5.1.2 and SpringData 2.1.2

Note: If I use @Transient annotation that persists the insert query correctly but then the field is being ignored completely even on read/fetch.

Many thanks for any help with this!

barbsan
  • 3,418
  • 11
  • 21
  • 28
user3732445
  • 241
  • 1
  • 10
  • use @Transient annotation on your Timestamp field. – Angad Bansode Nov 13 '18 at 13:22
  • If it isn't insertable or updatable how can it be `null`? Also your mapping is weird how can the `@IdClass` be the same as the entity class? – M. Deinum Nov 13 '18 at 14:04
  • @M.Deinum I had to add `@IdClass` so that I can have two `@Id`s here. As I mentioned, when I removed that the error still persisted. – user3732445 Nov 13 '18 at 19:47
  • I know what `@IdClass` is for, but it should be a separate class and not point to the entity. Also an `@Id` that isn't generated (you need to specify that) needs to be inserted. So basically your mapping is conflicting. You don't want a generated value, but don't want to insert it as. So hibernate needs to make a decisions. – M. Deinum Nov 13 '18 at 19:50
  • @AngadBansode please, read my post, I cannot use `@Transient` because I need to be able to fetch and read that value – user3732445 Nov 13 '18 at 19:50
  • @M.Deinum many thanks! I guess that must be the case. I will try to see if I can fix it by moving it to a different class ... – user3732445 Nov 13 '18 at 19:52

2 Answers2

0

Try using GenericGenerator and GeneratedValue in your code.

Add the needed annotation and give values to all other members in Reading class, except ts.

Here some examples.

Bala555
  • 119
  • 1
  • 6
  • Thank! I tried that and it didn't work, maybe I haven't come up with the right choice permutation yet. – user3732445 Nov 13 '18 at 09:57
  • Anyways, is it a good idea to mix javax.persistence.* org.hibernate.annotations.* annotations together?? GeneratedValue is javax and GenericGenerator is hibernate. – user3732445 Nov 13 '18 at 09:57
-1

As you say

I get an error from Postgres

If you check the docs it states:

Technically, a primary key constraint is simply a combination of a unique constraint and a not-null constraint.

That's also true for multi-column primary keys (see here)

So, if ts is part of your primary key in the database (as the @Id indicates) it's simply not possible to insert null values in that column.

IMO Hibernate/Spring got nothing to do with that as

insert into ms.time_series (ts, name, reading) values (NULL, 'sensor1', 10.0)

should be equivalent to

insert into ms.time_series (name, reading) values ('sensor1', 10.0)
tom
  • 1,455
  • 1
  • 8
  • 13
  • 1
    well if Hibernate/Spring have nothing to do with this, then the SQL query sent should be without the "ts" still. Also I said I tried to make the "ts" not part of the primary key and the error still persisted – user3732445 Nov 13 '18 at 19:43
  • `CREATE TABLE ms.time_series ( ts TIMESTAMP(0) DEFAULT ('now'::text)::timestamp(0), name VARCHAR(255), reading DOUBLE PRECISION, PRIMARY KEY(ts, name) );` in SQL console I can run a query `insert into ms.time_series (name, reading) values ('sensor1', 10)` no problem – user3732445 Nov 13 '18 at 19:49
  • 1
    Why are you creating a timestamp with now, convert that to text to convert that to a timestamp again? What is the reasoning behind that. – M. Deinum Nov 13 '18 at 19:50
  • 1
    @M.Deinum I copy pasted that from somewhere, I will fix it, but I don't believe it is the cause of the problem. The problem is that Spring/Hibernate is sending an incorrect SQL query which may be either caused by me improperly using the annotations or a bug in the framework itself. – user3732445 Nov 13 '18 at 19:57
  • It isn't an incorrect SQL, the SQL is perfectly valid. Spring has nothing to do with. The problem is the mapping. You want a non-generated ID and don't want to insert it. Hibernate has to make a decisions and decides that it needs to insert the currently assigned value. You need to tell hibernate that it should use a database generated value, else hibernate doesn't know how to handle it. It currently assumes client assigned/generated ids. – M. Deinum Nov 13 '18 at 20:00
  • @M.Deinum you are right, the Hibernate is confused. I guess I need to play with the `@GeneratedValue` but I don't know how. Whether I set it to AUTO, IDENTITY or TABLE it still doesn't work. Any idea how to set it up properly? – user3732445 Nov 13 '18 at 20:47
  • 1
    @M.Deinum I have tried all I could possibly. All the possible permutations of `@GeneratedValue` along with `@TableGenerator`. I have spent 3 days on this, so I opened this thread also https://stackoverflow.com/questions/53290636/how-to-set-up-jpa-annotations-to-generate-id-primary-key-on-the-database-level getting really desperate – user3732445 Nov 13 '18 at 22:51
  • Perhaps it would be best if you remove the default value in the database and simply assign ts in your application... – tom Nov 14 '18 at 05:16
  • "it's simply not possible to insert null values in that column" -- correct, but it's possible to _attempt_ to do it, which is an error. That's what the generated SQL does, and why it's an error. It's well-formed, but NOT valid! And definitely not equivalent to the desired statement. Simple example https://www.db-fiddle.com/f/hV41cNimtxemj1F6P2HQuE/2 – Matthew Read Apr 18 '19 at 16:06
  • @MatthewRead: Your "simple example" isn't very fitting, as the "SERIAL" datatype creates an auto-increment column in the table (which of course works), so it got very little to do with the original question ... https://www.db-fiddle.com/f/5RXdNXrj4B3gxEGKY7x5fu/0 shows that both statements fail for the same reason (and therefore, regarding the outcome are equivalent). – tom May 03 '19 at 13:40
  • Your example agrees with me that it is possible to _attempt_ to insert null. Which, again, is what OP's statement does and is the issue. https://www.db-fiddle.com/f/5RXdNXrj4B3gxEGKY7x5fu/1 shows that they are _not_ equivalent with a default value, which OP has. – Matthew Read May 03 '19 at 15:14
  • It's possible to attempt a lot of things :) But yes, with default values (which I overlooked) they're of course not equivalent... point taken. – tom May 03 '19 at 15:21
  • @user3732445 i am having the same problem. I am fetching a list from a view so it is a read only. And after adding updatable=false and insertable=false on the field it still calls insert into view. It however doesn't update which is correct. I was excited to see your thread but it seems there was no solution. What did you end up doing? I do not want the insert to be called. – Etch Dec 10 '20 at 13:03