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;
}
Related
I have a model which i want to save to database.
#Data
public class Model {
#Id
private UUID id;
private String name;
private ModelSettings settings;
#Data
static class ModelSettings {
boolean fuelEngine;
}
}
create table model
(
id uuid not null,
name varchar(25) not null,
settings jsonb
)
i try to save modelSettings as jsonb object using simple repository method save(), but i got error
ERROR: relation "settings" does not exist
i wrote custom Converter and i see when modelSettings is converted to json, but after prepare statement Spring Data try to save settings field to related table. How to tell Spring Data save field as json only, not row in related table?
Sorry, i forgot #WritingConverter with JdbcValue.
Hi Please use Embedded Annotation:
Embedded entities are used to have value objects in your java data model, even if there is only one table in your database.
#Data
public class Model {
#Id
private UUID id;
private String name;
#Embedded(onEmpty = USE_NULL)
private ModelSettings settings;
#Data
static class ModelSettings {
boolean fuelEngine;
}
}
You can not have an object in your entity and expect JDBC to save it as json for you.
you need to define a String column and write a converter for it for saving in and reading from database.
also some databases like Oracle supports json values but you have not mentioned which database you are using.
in Oracle database you can define a table including a json column as below:
CREATE TABLE "USERS"
(
"ID" NUMBER(16,0) PRIMARY KEY,
"USER_NAME" VARCHAR2(85) UNIQUE NOT NULL,
"PASSWORD" VARCHAR2(48) NOT NULL,
"PROFILE" NCLOB NOT NULL CONSTRAINT profile_json CHECK ("PROFILE" IS JSON),
"SETTINGS" NCLOB NOT NULL CONSTRAINT settings_json CHECK ("SETTINGS" IS JSON),
);
And you need to create your entity class as below:
#Entity
#Table(name = "Users")
public class User {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_GENERATOR")
#SequenceGenerator(name = "USER_GENERATOR", sequenceName = "USERS_SEQ", allocationSize = 1)
private Long id;
#Column(name = "USER_NAME")
private String userName;
#Column(name = "PASSWORD")
private String password;
#Lob
#Nationalized
#Column(name = "PROFILE",columnDefinition="NCLOB NOT NULL")
private String profile;
#Lob
#Nationalized
#Column(name = "SETTINGS",columnDefinition="NCLOB NOT NULL")
private String settings;
}
as you can see here profile and setting are my json columns.
So lets imagine following situation. I have an entity such as this:
#Entity
public class Price {
#Id
private int id;
#Column
private int amount;
private String currency;
}
And I have two tables:
CREATE TABLE currency (
id integer not null primary key,
name varchar
);
CREATE TABLE price (
id integer not null primary key,
amount integer,
currency_id integer references currency(id)
);
I want to tell Spring that when I access Price.getCurrency() I want to have whatever is stored in column "name" of the "currency" table. In other words, I want to connect two tables in one entity.
I can make currency a separate class, annotate the property with #OneTo... and get it like price.getCurrency().getName(). But I don't want a separate class, I just need this specific column.
I tried adding it via #SecondaryTable annotation like this:
#SecondaryTable(name = "currency",
pkJoinColumns = #PrimaryKeyJoinColumn(name = "id", referencedColumnName = "currency_id"))
But in this case Spring connect two tables by it's ids like this:
SELECT * FROM price LEFT JOIN price ON price.id = currency.id
And of course it is not working. So how do I do this? Is #SecondaryTable a correct way and if so how do I connect it through non-primary key column?
Yes, you can use #SecondaryTable:
#Entity
#Table(name = "price")
#SecondaryTable(
name = "currency",
pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "currency_id")
})
public class Price {
#Id
private int id;
#Column
private int amount;
#Column(table = "currency", name = "name")
private String currency;
}
I have a class 'Audit' that will hold the following details about an entity:
Time Created
Created by which application user
Time Updated
Updated by which application user
On the database, these fields are stored on the same table as the entity attributes. For example,
USER table:
CREATE TABLE USERS (
id BIGINT NOT NULL AUTO_INCREMENT,
display_name VARCHAR(30) NOT NULL,
active BOOLEAN NOT NULL DEFAULT FALSE,
created_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_user BIGINT NOT NULL DEFAULT 0,
updated_time TIMESTAMP NOT NULL DEFAULT 0,
updated_user BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
USER Class:
#Entity(name="USER")
#Table(name="USERS")
public class User implements Audited {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID", nullable=false, updatable=false)
private long id;
#Column(name="DISPLAY_NAME", nullable=false)
#NotNull
#Size(min=5, max=30)
private String displayName;
#Column(name="ACTIVE", nullable=false)
#NotNull
private boolean active;
private Audit audit;
}
AUDIT Class:
public class Audit {
private Date createdTime;
private User createdByUser;
private Date updatedTime;
private User updatedByUser;
}
On each entity table, the audit fields will be named the same. What is the best approach in mapping these fields?
The best way would be to make the Audit class as #Embeddable entity and use it in your all entities as an #Embedded property, which is almost what your are doing currently.
I am using ebean ORM in my java based play framework and following is my model
package models;
import java.util.*;
import javax.persistence.*;
import com.avaje.ebean.annotation.CreatedTimestamp;
import com.avaje.ebean.annotation.UpdatedTimestamp;
import play.db.ebean.*;
import play.data.validation.*;
#Entity
#Table(name = "coupons")
public class Coupon extends com.avaje.ebean.Model {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Constraints.Required
#Constraints.MaxLength(80)
#Constraints.MinLength(10)
private String title;
#Constraints.Required
#Constraints.MaxLength(1000)
#Constraints.MinLength(10)
private String description;
#Column(name = "created_at")
#CreatedTimestamp
private Date createdAt;
#Column(name = "updated_at")
#UpdatedTimestamp
private Date updatedAt;
#Column(name = "valid_from")
private Date validFrom = new Date();
#Column(name = "valid_to")
private Date validTo = new Date((long)2147483647*1000);
}
Generated sql file is
create table coupons (
id bigserial not null,
title varchar(255),
description varchar(255),
valid_from timestamp,
valid_to timestamp,
created_at timestamp not null,
updated_at timestamp not null,
constraint pk_coupons primary key (id)
);
drop table if exists coupons cascade;
Does ebean convert the constraints into sql file? How do I make sure my constraints are reflected in sql file also?
Ebean does not read any of the Play constraint annotations.
#Constraints.Required
#Constraints.MaxLength(80)
So the Required and MaxLength are not read and hence don't effect the generated DDL.
Ebean reads the JPA annotations and the javax.validation.constraints annotations #NotNull and #Size. For example, you could use:
#NotNull
#Size(max = 80)
Ebean uses only JPA Annotations.
You can check out the docs for the #Column annotation, which has options such as
#Column(nullable = false)
However, there are no options for minimum or maximum string length, or to ensure that the string is not empty.
I'd like to use unidirectional relationship for my JPA instances (Hibernate 4.3.10)
RegionalCountry -> RegionalArea1 -> RegionalArea2
#Entity
public class RegionalCountry {
#Id
#Column(unique = true, nullable = false, updatable = false, length = 36)
private String uuid = UUID.randomUUID().toString();
private String countryName;
private String countryCode;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private List<RegionalArea1> regionalArea1;
...//getters&setters
}
#Entity
public class RegionalArea1 {
#Id
#Column(unique = true, nullable = false, updatable = false, length = 36)
private String uuid = UUID.randomUUID().toString();
private String area1Name;
private String area1Code;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private List<RegionalArea2> regionalArea2;
...//getters&setters
}
#Entity
public class RegionalArea2 {
#Id
#Column(unique = true, nullable = false, updatable = false, length = 36)
private String uuid = UUID.randomUUID().toString();
private String area2Name;
private String area2Code;
...//getters&setters
}
Then I want to use Spring JpaRepository to fetch the country and its regional area instances by country code:
public interface RegionalCountryRepository extends JpaRepository<RegionalCountry, UUID> {
public RegionalCountry findOneByCountryCode(String countryCode);
}
When I call
regionalCountryRepository.findOneByCountryCode(countryCode);
SQLException occurs: Unknown column 'regionalar0_.regional_country' in 'field list'
the same exception is for regionalCountryRepository.findAll();
Is there a way to use unidirectional relationship between regional instances and do not get this error?
Surprisingly, unit tests with embedded db work well for the code above, but when we deal with the real MySQL database the exception occurs.
#Test
public void testFindByCountryCode() {
southernArea = new RegionalArea1();
southernArea.setArea1Code("00");
southernArea.setArea1Name("SOUTHERN");
chejuDoArea = new RegionalArea1();
chejuDoArea.setArea1Code("00");
chejuDoArea.setArea1Name("CHEJU-DO");
korea = new RegionalCountry();
korea.setCountryName("Korea, Republic of");
korea.setCountryCode("KOR");
korea.setRegionalArea1s(Arrays.asList(southernArea, chejuDoArea));
repository.save(korea);
RegionalCountry countryFetched = repository.findOneByCountryCode("KOR");
Assert.assertNotNull(countryFetched);
}
Update: the schema is the following
CREATE TABLE REGIONAL_AREA2(
UUID VARCHAR(36) NOT NULL,
AREA2CODE VARCHAR(255),
AREA2NAME VARCHAR(255)
);
CREATE TABLE REGIONAL_AREA1(
UUID VARCHAR(36) NOT NULL,
AREA1CODE VARCHAR(255),
AREA1NAME VARCHAR(255)
);
CREATE TABLE REGIONAL_AREA1_REGIONAL_AREA2(
REGIONAL_AREA1_UUID VARCHAR(36) NOT NULL,
REGIONAL_AREA2_UUID VARCHAR(36) NOT NULL
);
CREATE TABLE REGIONAL_COUNTRY(
UUID VARCHAR(36) NOT NULL,
COUNTRY_CODE VARCHAR(255),
COUNTRY_NAME VARCHAR(255)
);
CREATE TABLE REGIONAL_COUNTRY_REGIONAL_AREA1(
REGIONAL_COUNTRY_UUID VARCHAR(36) NOT NULL,
REGIONAL_AREA1_UUID VARCHAR(36) NOT NULL
);
There may be several reasons:
I do not see any mapping from your class variables to table field regional_country. Please make sure to use annotation #Column to specify the correct table field which the variable maps to. For instance, if variable countryCode maps to table field country_code, you should annotate like this: #Column(name = "country_code") private int countryCode;
For variables private String countryName; private String countryCode; if they have a mapping field in your db schema, please use #Column to annotate them
It would be helpful if you provide more information about your question: e.g., your db schema.
I try to reproduce your problem using just Hibernate Session without any JpaRepository. I have to change mapping for all collection associations from List to Set cause of the "cannot simultaneously fetch multiple bags" error
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<RegionalArea1> regionalArea1;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<RegionalArea2> regionalArea2;
Everything works fine without any errors. May be, It will help you too. If It will no help:
Hibernate thinks that the REGIONAL_COUNTRY_REGIONAL_AREA1 join table has the regional_country column, but this table has the REGIONAL_COUNTRY_UUID. So try to specify join column names by #JoinTable annotation, or recreate database schema, or change the naming strategy, if you specify it.
SQL generated by Hibernate for search country by country code
select
this_.f_uuid as f_uuid1_3_2_,
this_.f_country_code as f_countr2_3_2_,
this_.f_country_name as f_countr3_3_2_,
regionalar2_.fk_regional_country as fk_regio1_4_4_,
regionalar3_.f_uuid as fk_regio2_4_4_,
regionalar3_.f_uuid as f_uuid1_0_0_,
regionalar3_.f_area1code as f_area2_0_0_,
regionalar3_.f_area1name as f_area3_0_0_,
regionalar4_.fk_regional_area1 as fk_regio1_1_5_,
regionalar5_.f_uuid as fk_regio2_1_5_,
regionalar5_.f_uuid as f_uuid1_2_1_,
regionalar5_.f_area2code as f_area2_2_1_,
regionalar5_.f_area2name as f_area3_2_1_
from
spring_regional_countries this_
left outer join
spring_regional_countries_regional_area1s regionalar2_
on this_.f_uuid=regionalar2_.fk_regional_country
left outer join
spring_regional_area1s regionalar3_
on regionalar2_.fk_regional_area1=regionalar3_.f_uuid
left outer join
spring_regional_area1s_regional_area2s regionalar4_
on regionalar3_.f_uuid=regionalar4_.fk_regional_area1
left outer join
spring_regional_area2s regionalar5_
on regionalar4_.fk_regional_area2=regionalar5_.f_uuid
where
this_.f_country_code=?
Thanks for all answers but the problem was in REGIONAL_COUNTRY_REGIONAL_AREA1 table column names. Postfix _UUID at the end of the column name confuses MySQL.
It's possible to fix this problem renaming columns of intermediate datatables
DROP TABLE IF EXISTS REGIONAL_AREA1_REGIONAL_AREA2;
DROP TABLE IF EXISTS REGIONAL_AREA1_REGIONAL_AREA2;
CREATE TABLE REGIONAL_AREA1_REGIONAL_AREA2(
REGIONAL_AREA1 VARCHAR(36) NOT NULL,
REGIONAL_AREA2 VARCHAR(36) NOT NULL
);
CREATE TABLE REGIONAL_COUNTRY_REGIONAL_AREA1(
REGIONAL_COUNTRY VARCHAR(36) NOT NULL,
REGIONAL_AREA1 VARCHAR(36) NOT NULL
);