I'm developing an application with Spring Boot and hibernate, which connects to a postgres instance running in docker. When I first created my schema.sql, it looked like this:
CREATE TABLE groups(
group_id varchar(255) PRIMARY KEY,
group_desc varchar(255),
group_name varchar(255)
);
The table created successfully, however I soon realized 255 is too short for my purposes and changed my schema to the following:
CREATE TABLE groups(
group_id text PRIMARY KEY,
group_desc text,
group_name text
);
However, the database keeps reverting to the original data types. I've tried dropping the table, however when the Spring app runs and it gets created again as varchar(255) instead of text. How do I force hibernate to use the updated schema?
I've tried changing the spring.jpa.hibernate.ddl-auto property to create and update, and tried changing the fields to other datatypes, including other lengths of varchar. Nothing has worked so far. Even deleting schema.sql seemingly has no effect.
My application.properties looks like this:
spring.jpa.database=POSTGRESQL
spring.datasource.platform=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=<redacted>
spring.datasource.password=<redacted>
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
The Text datatype is not a Varchar but a CLOB.
Check your Groups class. I guess the name and desc attributes are String and the default related db type is then VARCHAR(255). Thus, if you generate your schema from your entity, String always become VARCHAR(255)
If you want to use Text, your field should be annotated with #Lob
public class Groups {
#Lob
#Column(name = "group_desc")
private String desc
#Lob
#Column(name = "group_name")
private String name
}
This being stated, I think you should change your java/db model because having a Lob/Text type as primary/foreign key frightens me a little (even if I never tried)
CREATE TABLE groups(
group_id bigint PRIMARY KEY,
group_code text NOT NULL UNIQUE,
group_desc text,
group_name text
);
public class Groups {
#Id
#Column(name = "group_id")
private Long id;
#Lob
#Column(name = "group_code", nullable = false, unique = true)
private String code;
#Lob
#Column(name = "group_desc")
private String desc;
#Lob
#Column(name = "group_name")
private String name;
}
NB : I usually don't generate the schema from entities so I instead use the code below to map a db text field to entity attribute :
#Column(name = "group_desc", columnDefinition = "CLOB")
private String desc;
But I am not sure if this is handled correctly to generate the schema
Related
Spring Boot 1.5.8 and Java 8 here. I've followed all the Spring Boot & Liquibase guides and I can't seem to get Liquibase to work.
Here is a link to a GitHub repo for reproducing the issue exactly, but here's the scoop:
I have the following MySQL v8 database that gets created like so ahead of time (before the app runs):
CREATE DATABASE IF NOT EXISTS troubleshooting_db CHARACTER SET utf8 COLLATE utf8_general_ci;
I have the following src/main/resources/db/changelog files:
db.changelog-master.yaml:
===
databaseChangeLog:
- include:
file: db/changelog/1-setup.sql
1-setup.sql:
===
--liquibase formatted sql
--changeset troubleshooting:1 dbms:mysql
-- LOOKUPS
CREATE TABLE IF NOT EXISTS metric_range_categories (
metric_range_category_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
metric_range_category_ref_id VARCHAR(36) NOT NULL,
metric_range_category_name VARCHAR(250) NOT NULL,
metric_range_category_label VARCHAR(250) NOT NULL,
metric_range_category_description VARCHAR(500) NOT NULL,
CONSTRAINT pk_metric_range_categories PRIMARY KEY (metric_range_category_id),
INDEX idx_metric_range_categories_metric_range_category_ref_id (metric_range_category_ref_id),
INDEX idx_metric_range_categories_metric_range_category_label (metric_range_category_label),
CONSTRAINT uc_metric_range_categories_metric_range_category_ref_id UNIQUE (metric_range_category_ref_id),
CONSTRAINT uc_metric_range_categories_metric_range_category_name UNIQUE (metric_range_category_name),
CONSTRAINT uc_metric_range_categories_metric_range_category_label UNIQUE (metric_range_category_label)
);
-- Lots of other CREATE TABLE statements down here...
And the following JPA-annotated entity:
#Entity
#Table(name = "metric_range_categories")
#AttributeOverrides({
#AttributeOverride(name = "id", column = #Column(name = "metric_range_category_id")),
#AttributeOverride(name = "refId", column = #Column(name = "metric_range_category_ref_id")),
#AttributeOverride(name = "name", column = #Column(name = "metric_range_category_name")),
#AttributeOverride(name = "label", column = #Column(name = "metric_range_category_label")),
#AttributeOverride(name = "description", column = #Column(name = "metric_range_category_description"))
})
public class MetricRangeCategory extends BaseLookup {
public MetricRangeCategory() {
}
public MetricRangeCategory(Long id, String refId, String name, String label, String description) {
super(id, refId, name, label, description);
}
}
At runtime I get the following exception:
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [metric_range_categories]
at org.hibernate.tool.schema.internal.SchemaValidatorImpl.validateTable(SchemaValidatorImpl.java:67)
at org.hibernate.tool.schema.internal.SchemaValidatorImpl.doValidation(SchemaValidatorImpl.java:50)
at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:91)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:475)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:444)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:879)
... 29 common frames omitted
So when it starts up, Liquibase does not excute/engage and so Hibernate JPA validation fails because its looking for a table that doesn't exist (because Liquibase never kicked in and did its job!). Any ideas as to where I'm going awry? Why isn't Liquibase kicking in?
There are 2 different problems in the repo:
Wrong location of application.yml. Move it from root to
src/main/resources
Nested property in TroubleshootingConfig.Machine
has null value, because of this bean "authInfo" is not created and context initialization fails. Here is the reference on how Spring Boot Configuration Binding works.
I'm using Hibernate 3.6.8 and I have a table defined in mysql (5.5) like this:
CREATE TABLE mytable
(
id BIGINT(20) PRIMARY KEY NOT NULL,
version INT(11),
description VARCHAR(60),
scheduled_at DATETIME,
`from` BIGINT(20),
`to` BIGINT(20),
deleted BIT(1) DEFAULT b'0',
completed BIT(1) DEFAULT b'0',
delete_from_after_completion BIT(1) DEFAULT b'0',
CONSTRAINT FK14F71A73C588I54F FOREIGN KEY (`from`) REFERENCES other_table (id),
CONSTRAINT FK14F71C231C45J198 FOREIGN KEY (`to`) REFERENCES other_table (id)
);
CREATE INDEX FK14F71A73C588I54F ON mytable (`from`);
CREATE INDEX FK14F71C231C45J198 ON mytable (`to`);
And a Java entity defined like this:
#Entity
public class MyTable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
#Column(name = "description")
private String description;
#Column(name = "scheduled_at")
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "SM")
private Date scheduledAt;
#Column(name = "from")
private Long fromId;
#Column(name = "to")
private Long toId;
#Column(name = "deleted")
private boolean deleted;
#Column(name = "completed")
private boolean completed;
#Column(name = "delete_from_after_completion")
private boolean deleteFromEntityAfterCompletion;
...
}
When I try to persist an instance of MyTable with valid values I end up with the following error:
2016-01-12 10:06:33,443 [qtp2139431292-20] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 1064, SQLState: 42000
2016-01-12 10:06:33,443 [qtp2139431292-20] ERROR org.hibernate.util.JDBCExceptionReporter - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'from, scheduled_at, to, version) values (0, 1, 0, 'Test', 10, '2016-01-13 00:00:' at line 1
I suspect that the problem occurs since I try to model the foreign key relationships (to and from) as Long instead of an entity (OtherTable). I suspect this because Hibernate can indeed persist this entity if I comment out the to and from fields. Note that the relationship to the to and from entities does indeed exists in the database so that's not the problem.
If I try insert manually using something like this it works:
insert into mytable values(3, 0, "desc", '2016-10-10 00:00:', 10, 11, 0, 0, 0);
You have a column that is a SQL reserved keyword ("from") and Hibernate doesn't bother quoting it for you. Other JPA implementations (e.g DataNucleus JPA) take care of such things for you. You will have to add single quotes around the reserved word in your JPA annotation information
I am pretty sure that the problem is not a matter of persisting Long values.
Your code has something wrong. I bet there is a wrong quotation mark in your insert code.
I use varbinary(36) to save UUID in MySql as primary keys. And I use EclipseLink to auto gen my data objects. It gens primary key as String. I inserted data as below. When I use JPA to select * from country, the COUNTRY_ID becames: '66653565373765302D663832312D313165332D613361632D303830303230306339613636'. What am I doing wrong?
INSERT INTO `didicity`.`country`
(`COUNTRY_ID`, `NAME`, `SHORT_NAME`)
VALUES
('fe5e77e0-f821-11e3-a3ac-0800200c9a66',
'United State',
'USA'
);
#Id
#Column(name = "COUNTRY_ID")
private String countryId;
I have a table containing customer data in an oracle database. Here is a simplified definition:
CUSTOMER (CUSTOMER_ID NUMBER NOT NULL,
SOURCE_SYSTEM VARCHAR2(30),
FULL_NAME VARCHAR2(360),
PHONE_NUMBER VARCHAR2(240)
)
The primary key for this table is (CUSTOMER_ID, SOURCE_SYSTEM).
The table has numerous rows for which SOURCE_SYSTEM is null. At the database level, there is no issue, however when I try to access any of these rows via JPA Entity, it causes a number of issues:
1: Using em.find() to fetch a row with a null SOURCE_SYSTEM always results in a null being returned.
2: Using em.merge() to upsert a row with a null SOURCE_SYSTEM succeeds if the record does not exist in the table, but fails on subsequent updates because the merge ALWAYS results in an insert being run.
3: Using em.createQuery() to explicitly query for a row with a null causes the following exception:
Exception [EclipseLink-6044] (Eclipse Persistence Services - 2.3.1.v20111018-r10243):
org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [ArrayRecord(
CUSTOMER.CUSTOMER_ID => 1
CUSTOMER.FULL_NAME => GUY PERSON
CUSTOMER.PHONE_NUMBER => 555-555-1234
CUSTOMER.SOURCE_SYSTEM => null)] during the execution of the query was detected to be null.
Primary keys must not contain null.
Query: ReadAllQuery(referenceClass=Customer sql="SELECT CUSTOMER_ID, FULL_NAME, PHONE_NUMBER, SOURCE_SYSTEM FROM CUSTOMER WHERE ((CUSTOMER_ID = ?) AND (SOURCE_SYSTEM IS NULL))")
Unfortunately, "Primary keys must not contain null" seems pretty final. I was unable to find too much information on workarounds for this error, which makes it seem like there is no solution.
THE QUESTION: I would like to know if anyone has any Java code-based solution that don't involve making changes to the database. My current workaround is to use ROW_ID as the #Id of the table, but this means I can no longer use em.merge() or em.find().
Here are my Java classes:
Customer.java:
#Entity
#Table(name = "CUSTOMER")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private Customer_Id key;
#Column(name = "CUSTOMER_ID", nullable = false, insertable = false, updatable = false)
private Long customerId;
#Column(name = "SOURCE_SYSTEM", length = 30, insertable = false, updatable = false)
private String sourceSystem;
#Column(name = "FULL_NAME", length = 360)
private String fullName;
#Column(name = "PHONE_NUMBER", length = 240)
private String phoneNumber;
//Setters, Getters, etc
...
}
Customer_Id.java
#Embeddable
public class Customer_Id implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "CUSTOMER_ID", nullable = false)
private Long customerId;
#Column(name = "SOURCE_SYSTEM", length = 30)
private String sourceSystem;
//Setters, Getters, etc
...
}
Primary keys cannot contain null (in JPA or in databases). Use a different value such as "" or " ".
Is the customer_id unique? if so then just remove the sourceSystem from the Id.
Otherwise, you could try logging a bug to have support for null ids added.
I'm testing JPA, in a simple case File/FileVersions tables (Master/Details), with OneToMany relation, I have this problem: in FileVersions table, the field "file_id" (responsable for the relation with File table) accepts every values, not only values from File table.
How can I use the JPA mapping to limit the input in FileVersion.file_id only for values existing in File.id?
My class are File and FileVersion:
FILE CLASS
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="FILE_ID")
private Long id;
#Column(name="NAME", nullable = false, length = 30)
private String name;
//RELATIONS -------------------------------------------
#OneToMany(mappedBy="file", fetch=FetchType.EAGER)
private Collection <FileVersion> fileVersionsList;
//-----------------------------------------------------
FILEVERSION CLASS
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="VERSION_ID")
private Long id;
#Column(name="FILENAME", nullable = false, length = 255)
private String fileName;
#Column(name="NOTES", nullable = false, length = 200)
private String notes;
//RELATIONS -------------------------------------------
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="FILE_ID", referencedColumnName="FILE_ID", nullable=false)
private File file;
//-----------------------------------------------------
and this is the FILEVERSION TABLE
CREATE TABLE `JPA-Support`.`FILEVERSION` (
`VERSION_ID` bigint(20) NOT NULL AUTO_INCREMENT,
`FILENAME` varchar(255) NOT NULL,
`NOTES` varchar(200) NOT NULL,
`FILE_ID` bigint(20) NOT NULL,
PRIMARY KEY (`VERSION_ID`),
KEY `FK_FILEVERSION_FILE_ID` (`FILE_ID`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
Thanks for help,
I know the SQL constraint to limit the input, but it is possible to create this SQL costraint using some annotation, without writing by hand the SQL in the database?
I'm new on JPA, I was thinking that using #JoinColumn annotation, JPA could create also the costraint...
Thank you again.
At the Java level, you describe and annotate associations between classes - which and you did - and your mapping looks fine.
At the database level, if you want to restrict the possible values in the file_id column to values that are primary keys in the FILE table, you should use a foreign key constraint. To do so, you will need to use InnoDB tables. Something like that:
CREATE TABLE `JPA-Support`.`FILEVERSION` (
`VERSION_ID` bigint(20) NOT NULL AUTO_INCREMENT,
`FILENAME` varchar(255) NOT NULL,
`NOTES` varchar(200) NOT NULL,
`FILE_ID` bigint(20) NOT NULL,
PRIMARY KEY (`VERSION_ID`),
FOREIGN KEY `FK_FILEVERSION_FILE_ID` (`FILE_ID`) REFERENCES FILE(ID)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
The table FILE also has to use InnoDB. Actually, use InnoDB tables for the tables for which you want to use referential integrity.