JPA using #SQLInsert throws Parameter index out of range - java

#Data
#MappedSuperclass
public class BaseModel implements Serializable {
private static final Long serialVersionUID = -1442801573244745790L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime createAt = LocalDateTime.now();
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#Convert(converter = LocalDateTimeConverter.class)
private LocalDateTime updateAt = LocalDateTime.now();
}
#Data
#Entity
#Table(name = "tb_vip_code")
#SQLInsert(sql = "insert ignore into tb_vip_code (code, duration) values (?, ?)")
public class VipCode extends BaseModel {
private static final Long serialVersionUID = -4697221755301869573L;
private String code;
private Integer duration;
private Integer status = 0;
private Long userId;
public VipCode() {}
}
#Test
public void addOne() throws Exception {
VipCode vipCode = new VipCode();
vipCode.setCode("123456");
vipCode.setDuration(1);
service.addOne(vipCode);
}
create table tb_vip_code
(
id bigint auto_increment
primary key,
code varchar(20) not null,
status int default '0' not null,
user_id bigint null,
duration int not null,
create_at datetime default CURRENT_TIMESTAMP not null,
update_at timestamp default CURRENT_TIMESTAMP not null,
constraint tb_vip_code_code_uindex unique (code)
);
All code as above, I am trying to use a custom sql to save object, but it throws an exception:
Caused by: java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2).
I only have two parameters; why does it say it needed three?

The SQL statement specified by #SQLInsert gets the fields of your entity bound as parameters.
You have at least 4 attributes in your VipCode entity directly, and I suspect even more from the BaseModel class it inherits from. But your SQL statment expects only two parameters.
To fix the problem remove the superfluous attributes from the entity, add parameters for them to the query or mark them as #Transient.

Related

#UpdateTimestamp does not update the value on second save

I'm having the following entity class
#Entity
#Builder
#Table(name = "foo")
#Data
#NoArgsConstructor
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
...
#Column(name = "created_at", updatable = false)
#CreationTimestamp
private Instant createdAt;
#Column(name = "updated_at")
#UpdateTimestamp
private Instant updatedAt;
}
And expect that the createdAt field will be set only once on the first save() action, and the updatedAt field will be updated on every save() action.
However, with the following test:
#Test
public void shouldUpdateFieldUpdatedAt() {
var savedFoo = fooRepository.save(new Foo());
var firstUpdate = savedFoo.getUpdatedAt();
var updatedFoo = fooRepository.save(savedFoo);
assertThat(firstUpdate).isBefore(updatedFoo.getUpdatedAt());
}
It fails all the time. And I can see in the debug that for two distinct Foo instances there updatedAt field is the same.
I'm using Hibernate 5.3.7 which does support Instant types. So, I have no clue what is the issue...
The DB is Postgres with the following table:
CREATE TABLE IF NOT EXISTS foo (
id SERIAL PRIMARY KEY,
...
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
);
Looks like the issue could be with the hibernate caching.
If between two save() actions I update some other field the test passes and the updatedAt is different.
#Test
public void shouldUpdateFieldUpdatedAt() {
var savedFoo = fooRepository.save(new Foo());
var firstUpdate = savedFoo.getUpdatedAt();
savedFoo.setTitle("New Title");
var updatedFoo = fooRepository.save(savedFoo);
assertThat(firstUpdate).isBefore(updatedFoo.getUpdatedAt());
}

DynamicUpdate added extra untouched column in update statement

I am facing a weird behavior using #DynamicUpdate with one of my entity. So my entity is defined like this
#Entity
#DynamicUpdate
#Table(name = "courts")
#Getter
#Setter
#Builder
#AllArgsConstructor // require for #Builder to work correctly
#NoArgsConstructor // required for hibernate mapping
public class CourtDO {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// name is SQL keyword
#Column(name = "court_name")
private String name;
#Column
private String addressStreet;
#Column
private String addressWard;
#Column
private String addressDistrict;
#Column
private String addressCity;
#Column
private String addressCountry;
#Column
private String phoneNumber;
#Column(nullable = false)
#Convert(converter = DOTimestampConverter.class)
#CreationTimestamp
private ZonedDateTime createdAt;
#Column(nullable = false)
#Convert(converter = DOTimestampConverter.class)
#UpdateTimestamp
private ZonedDateTime updatedAt;
}
DOTimestampConverter is just a simple AttributeConverter to convert ZonedDateTime to ms for me to store in DB as number.
As you can see I marked the entity with #DynamicUpdate.
I have a small jersey REST-API which allow me to update all of the normal field beside the id and the generated date. I always check if the input is null before actually touching the setter for the field.
I see a very weird behavior with the column address_city, it would be included in every update like this even if I only touch other fields, in this case, just the name which translate to court_name because name is a reserved keyword in SQL. UPDATE Actually the problem is not with just the address_city column. Even if I ignore that column, other column would get included in the update statement.
Hibernate:
update
courts
set
address_city=?,
court_name=?,
updated_at=?
where
id=?
Where as if I only edit the address_city only then I would see it correctly
Hibernate:
update
courts
set
address_city=?,
updated_at=?
where
id=?
Did anyone face this problem before?
The stack I am using are
spring-boot 2.1.3.RELEASE
spring-boot-starter-data-jpa
postgresql 11.4
And the data schema
CREATE TABLE COURTS (
id SERIAL PRIMARY KEY,
court_name VARCHAR(20),
address_street VARCHAR(128),
address_ward VARCHAR(20),
address_district VARCHAR(20),
address_city VARCHAR(20),
address_country VARCHAR(10),
phone_number VARCHAR(20),
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
This is how update through REST-API
#Override
public Court editCourt(String courtId, CreateCourtRequest createCourtRequest) {
Optional<CourtDO> court = courtRepository.findById(NumberUtils.toLong(courtId));
return court
.map(courtDO -> editCourtInfo(courtDO, createCourtRequest))
.map(courtRepository::save)
.map(courtDOToResponseConverter::convert)
.orElse(null);
}
private CourtDO editCourtInfo(CourtDO courtDO, CreateCourtRequest createCourtRequest) {
if (StringUtils.isNotBlank(createCourtRequest.getName())) {
courtDO.setName(createCourtRequest.getName());
}
if (StringUtils.isNotBlank(createCourtRequest.getAddressStreet())) {
courtDO.setAddressStreet(createCourtRequest.getAddressStreet());
}
if (StringUtils.isNotBlank(createCourtRequest.getAddressWard())) {
courtDO.setAddressWard(createCourtRequest.getAddressWard());
}
if (StringUtils.isNotBlank(createCourtRequest.getAddressDistrict())) {
courtDO.setAddressDistrict(createCourtRequest.getAddressDistrict());
}
if (StringUtils.isNotBlank(createCourtRequest.getAddressCity())) {
courtDO.setAddressCity(createCourtRequest.getAddressCity());
}
if (StringUtils.isNotBlank(createCourtRequest.getPhoneNumber())) {
courtDO.setPhoneNumber(createCourtRequest.getPhoneNumber());
}
return courtDO;
}

Java Hibernate weird marshaling bug?

I am having an issue with Spring Data JPA when it returns data from the database. I'm giving this one more shot on here. Below is the setup.
Spring V 1.5.15.RELEASE
PGV 9.5.4
SQL:
CREATE TABLE dmg.gps_user_to_data_set (
group_id int8 NOT NULL,
data_set int4 NOT NULL,
agency_id int8 NOT NULL,
FOREIGN KEY (agency_id) REFERENCES funding_agency(agency_id),
FOREIGN KEY (group_id, data_set) REFERENCES data_set(group_id, data_set)
)
WITH (
OIDS=FALSE
) ;
Repo:
public interface GpsUserToDataSetTestRepository extends JpaRepository<GpsUserToDataSetTest, DataSetPK> {
#Query(value = "select group_id,data_set,agency_id from gps_user_to_data_set where group_id=?1 and data_set=?2", nativeQuery = true)
List<GpsUserToDataSetTest> test(Long groupId, Long dataSet);
#Query(value = "select group_id,data_set,agency_id from gps_user_to_data_set where group_id=?1 and data_set=?2", nativeQuery = true)
List<Object> test2(Long groupId, Long dataSet);
}
Entity:
#Entity
#Data
#Table(name = "GPS_USER_TO_DATA_SET")
public class GpsUserToDataSetTest implements Serializable {
#EmbeddedId
private DataSetPK primaryKey;
#Column(name = "AGENCY_ID")
private Long agencyId;
}
Test:
System.out.println("ID: "+dataSetPK);
System.out.println("YYY : ");
gpsUserToDataSetTestRepo.test(dataSetPK.getGroupId(), dataSetPK.getDataSetId()).stream().forEach(e -> {
try {
System.out.println(new ObjectMapper().writeValueAsString(e));
} catch(JsonProcessingException jpe) {
}
});
System.out.println("ZZZ: ");
gpsUserToDataSetTestRepo.test2(dataSetPK.getGroupId(), dataSetPK.getDataSetId()).stream().forEach(e -> {
try {
System.out.println(new ObjectMapper().writeValueAsString(e));
} catch(JsonProcessingException jpe) {
}
});
So I run this and I get two different results, I would expect when the interface GpsUSerToDataSetTest the encapsulated information should be exacly the same as when I run the same query with java.lang.Object, but you can see the ids are not the same, below is the data in the DB & the stdOut dump.
I dont even know what to say here, this just seems very odd to me, and potentially a bug?
DB:
group_id |data_set |agency_id |
---------|---------|----------|
1356 |1 |2 |
1356 |1 |2 |
1356 |1 |19 |
Dump:
ID: DataSetPK(dataSetId=1, groupId=1356)
YYY :
{"primaryKey":{"dataSetId":1,"groupId":1356},"agencyId":2}
{"primaryKey":{"dataSetId":1,"groupId":1356},"agencyId":2}
{"primaryKey":{"dataSetId":1,"groupId":1356},"agencyId":2}
ZZZ:
[1356,1,2]
[1356,1,2]
[1356,1,19]
as you can see the EXACT same query returns two different results on the agencyId as soon as I add the GpsUserToDataSetTest to the result set for marshaling. Any feedback would be good, I am lost at this point.
EDIT:
Updated Entity:
public class GpsUserToDataSetTest implements Serializable {
// #EmbeddedId
// private DataSetPK primaryKey;
#Column(name = "DATA_SET")
#Id
private Long dataSetId;
#Column(name = "GROUP_ID")
#Id
private Long groupId;
#Column(name = "AGENCY_ID")
private Long agencyId;
}
Edit based on Comments:
#Entity
#Data
#Table(name = "GPS_USER_TO_DATA_SET")
public class GpsUserToDataSetTest implements Serializable {
// #EmbeddedId
// private DataSetPK primaryKey;
#Column(name = "DATA_SET")
private Long dataSetId;
#Column(name = "GROUP_ID")
private Long groupId;
#Column(name = "AGENCY_ID")
#Id
private Long agencyId;
}
ID: DataSetPK(dataSetId=1, groupId=1356)
YYY :
{"dataSetId":1,"groupId":1356,"agencyId":2}
{"dataSetId":1,"groupId":1356,"agencyId":2}
{"dataSetId":1,"groupId":1356,"agencyId":19}
ZZZ:
[1356,1,2]
[1356,1,2]
[1356,1,19]
In case the query result is captured into List<GpsUserToDataSetTest> in test1() the GpsUserToDataSetTest ORM objects are populated and added to the list. Hence, the JSON converted string is the exact representation of the ORM.
On the other hand, when the query result is captured into List<Object> in test2() these are populated as mere values array and then added to the list. Hence, the JSON representation has only values array.
Write a new method test3() in the repository and capture the results into List<Map<String, String>> and observe the output. You'll notice plain individual key values (no field like primaryKey etc.) in the JSON string.
Edit
Also, all the rows returned from test1() is duplicate because of the primary key (#Id or #EmbeddedId) fields in the rows returned are the same. Hibernate cache is the cause here. This is not a bug in Hibernate rather the data is the database should be fixed or the #Id annotation should be used correctly.

Spring JpaRepository getOne() for table with PK as String

I am using Spring Boot with a JpaRepository. I have a table defined with a PK that is a String.
CREATE TABLE mytable (
uuid uuid DEFAULT gen_random_uuid() PRIMARY KEY,
url text NOT NULL,
status status DEFAULT 'pending' NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL
);
Question
In the JpaRepository, how do I do the equivalent of getOne(uuid)? (getOne(uuid) receives a paramater type Long) i.e. How do I retrieve one row for a uuid that is type String?
Code
#Entity
#Table(name = "mytable")
public class MyTableEntity {
public enum Status {
pending,
complete,
processing,
failed
}
#Id
#Column(name = "uuid")
//#GeneratedValue(strategy = GenerationType.AUTO)
private String uuid;
#Column(name = "url", nullable = false)
private String url;
#Enumerated(EnumType.STRING)
#Column(columnDefinition = "photo_status", nullable = false)
//#Type( type = "pgsql_enum" )
private Status status;
#Column(name = "created_at", nullable = false)
private LocalDateTime created_at;
Repostory
public interface MyRepository extends JpaRepository<MyTableEntity, Long> {
}
So if your entity has String key you can use such declaration of your Repository :
#Repository
public interface MyRepository extends JpaRepository<MyTableEntity, String> {
}
Now if you want to get entity by id, which is of type String, you can use for example :
private void someMethod() {
UUID uuid = UUID.randomUUID();
MyTableEntity myEntity = myRepository.getOne(uuid.toString());
}
If you look at JpaRepository definition it expects first type to be your Entity class, and second is type of the key of this entity :
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>

Autogenerate Timestamp as Id for database?

#Id attribute has in general the ability to auto generate database primary key ids.
I'd like to have a timestamp is Id (I can ensure that there will be no 2 same timestamps).
Would you set the timestamp directly to the property of the variable, or initilize it within the constructor, or pass it as parameter in the constructor?
#Entity
public class MyDomain implements Serializable {
#Id
private Timestamp timestamp = new Timestamp(new Date().getTime());
public MyDomain() {
this.timestamp = new Timestamp(new Date().getTime());
}
public MyDomain(Timestamp timestamp) {
this.timestamp = timestamp;
}
}
What would you chose, and why?
I would not do that, use timestamp as an ID. The id filed should not have any business values at all(at least in my experience so far), I would add another timestamp row. But that's just my opinion.
I would avoid using a timestamp as the primary key. It's clumsy for a unique column. Also do you really want to be using a cumbersome field like that as a foreign key in your other tables?
If this timestamp genuinely needs to be unique then you should probably put timestamp in its own field and use a unique constraint.
#Entity
#Table(uniqueConstraints={#UniqueConstraint(columnNames={"timestamp"})})
public class MyDomain implements Serializable {
#Id
#Column(name = "id")
private Long id;
#Column(name = "timestamp")
public Timestamp timestamp;
}

Categories

Resources