liquibase exception unknown data type "JSON" - mysql

my application use jhipster with mysql and liquibase, and mysql version is 5.7.20 which support json column, and I use ObjectMapper to map json column and java object, and it works, the column type is json, but when the liquibase in involved (./mvnw package -Pprod dockerfile:build), there will be exception like "unknown data type 'JSON'" and the test will be failed, no docker image generated.
I change a bit in 20180410012441_added_entity_Ability.xml for that json column:
<column name="abilities" type="json">
<constraints nullable="true"/>
</column>
in my domain class, the json related field is like:
#Type(type = "json")
#Column(columnDefinition = "json")
private List<Skill> abilities = new ArrayList<>();
I suspect the problem is related for that xml column/type definition, liquibase does not support json keyword, I have no idea what should be there for correct type.
Could someone help please? very appreciated.
=========================================================
update 9:01 PM Beijing time
I add a changeSet as "
<changeSet id="20180415081741-1" author="jhipster">
<sql dbms="mysql" endDelimiter="\nGO" splitStatements="true"
stripComments="true">ALTER TABLE `ability` ADD `abilities` json DEFAULT NULL</sql>
</changeSet>
then I issue "./mvnw package -Pprod dockerfile:build", another exception which seemingly hibernate related:
2018-04-15 20:56:52.186 ERROR 23936 --- [ main] o.h.metamodel.internal.MetadataContext : HHH015007: Illegal argument on static metamodel field injection : com.james.app.domain.Ability_#abilities; expected type : org.hibernate.metamodel.internal.SingularAttributeImpl; encountered type : javax.persistence.metamodel.ListAttribute

For those who still have this problem in either H2 and PostgreSQL database even after defining a TypeDef ...etc, after doing the following:
#Entity
#TypeDef(name = "jsonb", typeClass = com.vladmihalcea.hibernate.type.json.JsonBinaryType.class)
class SomeEntity {
// ...
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private List<Skill> abilities = new ArrayList<>();
// ...
}
You need to register the type by extending the Database dialect and using that extended dialect as the dialect instead of the original dialect:
1st define the new extended dialect:
import org.hibernate.dialect.PostgreSQL10Dialect;
import javax.inject.Inject;
public class AMPostgresDialect
extends PostgreSQL10Dialect
{
#Inject
public AMPostgresDialect()
{
super();
registerColumnType( Types.JAVA_OBJECT, "jsonb" );
}
}
and if you have an H2 Database for the testing profile:
import org.hibernate.dialect.H2Dialect;
import java.sql.Types;
public class DhisH2Dialect extends H2Dialect
{
public DhisH2Dialect()
{
super();
registerColumnType( Types.JAVA_OBJECT, "text" );
registerColumnType( Types.JAVA_OBJECT, "json" );
}
and then in the application-*.yml properties file you use those extended dialects instead of the original one.

Change the liquibase column type as jsonb and try.
<column name="abilities" type="jsonb">
<constraints nullable="true"/>
</column>
Add the type and column definition as jsonb.
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private List<Skill> abilities = new ArrayList<>();

Considering you are using Oracle, Check this https://docs.oracle.com/en/database/oracle/oracle-database/12.2/adjsn/creating-a-table-with-a-json-column.html#GUID-E6CC0DCF-3D72-41EF-ACA4-B3BF54EE3CA0 for creating table with JSON column. Use the command in <sql> </sql> in liquibase.

Related

Liquibase generate migration for geometry type (Spring boot with JPA/Hibernate)

I am writing a RESTfull API with Spring boot using Maven Liquibase to manage migrations alongside MySQL 8 for the database.
I have searched online (1,2, 3) but Liquibase is still generating "TINYBLOB" type in migrations instead of "POINT" or "GEOMETRY". Surprisingly, when I edit the migration file (i.e. changeSet and use "POINT", mvn liquibase:update still creates a TINYBLOB column on the database.
I have a typal JPA entity:
import org.locationtech.jts.geom.Point;
#Entity
class MyModel {
private Point location;
// more fields
I am using Liquibase version 4.3 and Hibernate version 5.4. For hibernate dialect, I am using org.hibernate.spatial.dialect.mysql.MySQL8SpatialDialect.
It appears to me that spatial types are not supposed by Liquibase... but that would be surprising. Any help would be greatly appreciated (all other data types are behaving as expected).
Ran into the same issue some time ago and ended up manually overwriting parts of the auto-generated migration file. Worked fine for MySQL 8.
<!-- Define the type-->
<property name="pointType" value="geometry" dbms="h2"/> <!-- Only relevant for in-memory integration tests-->
<property name="pointType" value="POINT" dbms="mysql, oracle, mssql, mariadb, postgresql"/>
<!-- Use the type on the column-->
<column name="location" type="${pointType}">
<constraints nullable="true" />
</column>
A simplified version of my Hibernate model.
package com.stackoverflow.sample.domain;
import com.vividsolutions.jts.geom.Point;
import org.hibernate.annotations.Type;
import org.springframework.data.elasticsearch.annotations.FieldType;
import javax.persistence.*;
import java.io.Serializable;
#Entity
#Table(name = "some_entity")
public class SomeEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#org.springframework.data.elasticsearch.annotations.Field(type = FieldType.Keyword)
private Long id;
#Type(type = "com.vividsolutions.jts.geom.Point")
#Column(name = "location", nullable = false, columnDefinition = "geometry")
private Point location;
}

Using Spring Data with PostgreSQL JsonB type properties

I'm having hard time trying to use PostgreSQL with JsonB data type in Spring Data. I have the following table:
create table mytable (
...
MYDATA JsonB not null,
...
);
Then, I have the following entity:
#Entity
#Table(name = "mytable")
...
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class MyEntity {
...
#NotNull
#Type(type = "jsonb")
#Column(name = "MYDATA", nullable = false)
private MyAbstractData myAbstractData;
...
}
MyAbstractData is defined as follows:
...
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#Type(value = MyConcreteData.class, name = "data")
})
...
public abstract class MyAbstractData implements Serializable {
private String key;
...
}
Last but not least, MyConcreteData looks like below:
public class MyConcreteData extends MyAbstractData {
...
}
My repository is as follows:
#Repository
public interface MyEntityRepo extends CrudRepository<MyEntity, Long> {
public Optional<MyEntity> findByIdAndSomething (Long id, Something something);
}
In the database, I'm able to insert the value, for example, '{"type":"data","key":"i1"}' but, doing the following:
Optional<MyEntity> myEntity = myEntityRepo.findByIdAndSomething(1L, something);
I get the following exception:
org.springframework.orm.hibernate5.HibernateSystemException: java.lang.IllegalArgumentException: The given string value: "{\"type\":\"data\",\"key\":\"i1\"}" cannot be transformed to Json object; nested exception is org.hibernate.HibernateException: java.lang.IllegalArgumentException: The given string value: "{\"type\":\"data\",\"key\":\"i1\"}" cannot be transformed to Json object
However, I'm able to serialize/deserialize successfully the class MyConcreteData using the fasterxml ObjectMapper and to create a valid JsonNode from the string "{"type":"data","key":"i1"}".
I know that my case might be difficult to follow but I'm still hoping that someone could help.
Many thanks in advance.
Seymour
I'm answering my own question. In order to get working the sample shown above, the script used to insert data test in the H2 database should use FORMAT JSON syntax, for example:
INSERT INTO MYTABLE (..., MYDATA, ...)
VALUES (..., '{"type":"data","key":"i1"}' FORMAT JSON,...);
So, the clue here is to use FORMAT JSON immediately after the JSON payload in the insert statement. This way the find operation done by Spring Data works as expected.

How to properly set-up Spring Boot & Hibernate using InteliJ?

As the title suggests I am building a web application using Spring Boot and Hibernate for my data access layer and the development is done on InteliJ IDEA 14.1.2.
My Knowledge
This is my first time using Spring Boot, Hibernate and InteliJ. I have built a few small apps to test Spring Boot and Hibernate, but the complexity difference between those and the one I am building now is a bit bigger.
Environment
Regarding my environment, in case it matters, I am running Windows 7 SP1 64bit, MySQL server 5.6.17, InteliJ 14.1.2 and Ubuntu Server 14.04 on a VirtualBox 4.3.26 VM hosting a Redis 3.0.1 server.
Purpose
The purpose of using the above technologies at this point in time is the storage and retrieval of different entities to a MySQL database (Redis is used only for session externalization and sharing among app instances). In other words, I am building my data access layer.
Database
My complete database schema can be found here:
https://dl.dropboxusercontent.com/u/49544122/so/DB.pdf
Source
My Spring Boot application is the following:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import se.domain.cvs.abstraction.dataaccess.AccountRepository;
import se.domain.cvs.domain.AccountEntity;
#SpringBootApplication
#EnableRedisHttpSession
public class Application implements CommandLineRunner {
#Autowired
AccountRepository repository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... strings) throws Exception {
System.out.println("=======================================================");
AccountEntity account = repository.findByEmail("r.franklin#companya.se");
System.out.println("My name is " + account.getFirstName() + " " + account.getLastName());
System.out.println("=======================================================");
}
}
I am using CommandLineRunner interface just to test the bare data access layer without introducing REST endpoints yet.
My configuration is the following in YAML format:
...
# MySQL Database Configuration
spring.datasource:
url: jdbc:mysql://localhost:3306/cvs
username: cvs
password: cvs
driverClassName: com.mysql.jdbc.Driver
spring.jpa:
database: MYSQL
show-sql: true
hibernate.ddl-auto: validate
hibernate.naming-strategy: org.hibernate.cfg.DefaultNamingStrategy
properties.hibernate.dialect: org.hibernate.dialect.MySQL5Dialect
...
The JPA entities are automatically generated with InteliJ and that is where the problems begin. Let's take for example the OrderEntity below (for the sake of brevity I omit some code):
...
#Entity
#Table(name = "order", schema = "", catalog = "cvs")
public class OrderEntity {
...
private int invoiceId;
...
private InvoiceEntity invoiceByInvoiceId;
...
#Basic
#Column(name = "InvoiceID", nullable = false, insertable = false, updatable = false)
public int getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(int invoiceId) {
this.invoiceId = invoiceId;
}
...
#ManyToOne
#JoinColumn(name = "InvoiceID", referencedColumnName = "InvoiceID", nullable = false)
public InvoiceEntity getInvoiceByInvoiceId() {
return invoiceByInvoiceId;
}
public void setInvoiceByInvoiceId(InvoiceEntity invoiceByInvoiceId) {
this.invoiceByInvoiceId = invoiceByInvoiceId;
}
...
}
When trying to run the Spring Boot application I get the following error:
org.hibernate.MappingException: Repeated column in mapping for entity: OrderEntity column: invoiceId (should be mapped with insert="false" update="false")
After doing a little bit of research, I guess the problem is that the invoiceID now has two ways to be set, one through the setInvoiceID() setter and one through the InvoiceEntity object itself that the OrderEntity relates to, which could lead to an inconsistent state. As another user here puts it,
You would do that when the responsibility of creating/udpating the related entity in question isn't in the current entity.
See related post here: Please explain about: insertable=false, updatable=false
Setting the proposed values of the corresponding field (insertable and updateable) to false fixes the error.
My question here is why is this generated the wrong way? My change fixed the error, but I want to make sure that there is no errors in my SQL that lead InteliJ to generate this the wrong way. The complete SQL script can be found here http://pastebin.com/aDguqR1N.
Additionally, when generating the Entities, InteliJ requires a Hibernate config file which I guess Spring Boot generates on its own somewhere else (or uses Java based configuration). Whether I leave it there or delete it, it doesn't seem to affect the app at all. I guess the order taken by SB to read properties overrides it. Is it OK that I just remove it?
Thank you very much for your time and help in advance and sorry for this long post! :)
my advice is to let Spring/Hibernate let generate your db schema for you ( everything including foreign keys and constraints can be generated by Spring.
For me the folloeing approach worked:
in the parent entity(in my case the TblUser):
#OneToMany(targetEntity=TblTracks.class,fetch=FetchType.EAGER,cascade=CascadeType.ALL,mappedBy="tbluser")
private List<TblTracks> tbltracks= new ArrayList<TblTracks>();
where mappedBy points to the Tbluser Entity (private TblUser tbluser) of the child Entity
and in the child entity (in my case TblTracks) like
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="idTblUser",nullable=false)
private TblUser tbluser;

Grails: Joda LocalDate as unique key in a MySQL database

I have a domain class with a Joda LocalDate property. This property must be unique.
It works using an H2, but using a MySQL db I have this error on app boot:
[SchemaExport.create(l.386)]Unsuccessful: create table [...]
[SchemaExport.create(l.387)]BLOB/TEXT column 'mydate' used in key specification without a key length
If a remove unique constraint, it works also with MySQL.
Is it a bug or my misunderstanding?
I'm using Grails 2.2.5.
Here domain fragment:
class MyClass {
LocalDate mydate
static constraints = {
mydate(nullable:false, unique:true)
}
}
DataSource config fragment:
dataSource {
dbCreate = "create-drop"
driverClassName = "com.mysql.jdbc.Driver"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
username = "xxx"
password = "xxx"
dbNamer = "myapp"
url = "jdbc:mysql://localhost:3306/${dbNamer}?autoreconnect=true"
logSql = true
}
wild guess, I think that date is reserved word in mySQL. rename the field and try again
UPDATE:
ok, I see.
the problem is, that GORM/Hibernate is not aware of joda LocalDate class, so it's trying to create a BLOB column for it:
[SchemaExport.create(l.387)]BLOB/TEXT column 'mydate'`.
To fix it, you can either use a custom hibernate type (have no idea how), or convert the property into a plane java's Date class by saving, and back to LocalDate after loading

Serializing objects with many-to-many relationships with Spring MVC, Jackson, and Hibernate

I am having a difficult time figuring out how to properly define and annotate the models in my web application so that they can be effectively used in both a web interface and REST web service. Here is a simplified version of the relationship that is giving me trouble:
Post model:
#Entity
#Table(name = "POST")
public class Post implements Serializable {
#Id
#Column(name = "POST_ID")
#GeneratedValue(strategy-GenerationType.AUTO)
private Integer postId;
#Column(name = "POST_BODY")
private String postBody;
#ManyToMany(fetch = FetchType.EAGER)
#Cascade({CascadeType.SAVE_UPDATE})
#JoinTable(name = "POST_TAGS",
joinColumns={#JoinColumn(name="POST_ID")},
inverseJoinColumns={#JoinColumn(name="TAG_ID")})
private Set<Tag> tags = new HashSet<Tag>();
//Getters and setters...
}
Tag model
#Entity
#Table(name = "TAG")
public class Tag implements Serializable {
#Id
#Column(name = "TAG_ID")
#GeneratedValue(strategy-GenerationType.AUTO)
private Integer tagId;
#Column(name = "TAG_NAME")
private String tagName;
#ManyToMany(fetch = FetchType.EAGER, mappedBy="tags")
private Set<Post> posts = new HashSet<Post>();
//Getters and setters...
}
I have one web service controller method for fetching all of the posts and one method for fetching all of the tags. Each method should ideally return the list of the target class plus referenced classes. For example:
[{
postId: 1,
postBody: "Hello world!",
tags: [{
tagId: 1,
tagName: "hello"
},{
tagId: 2,
tagName: "message"
}]
}, {
postId: 2,
....
}]
But What I wind up seeing is an infinite recursion, as the returned tags for each post retrieve their associated post, which retrieve their associated tags, etc to infinity. I have tried using #JsonIgnore on the getter methods of my classes, but then I am no longer getting the referenced objects. I have tried using #JsonIdentityInfo to add IDs to my objects, but then I still get recursion, but only to the point where all references between the retrieved objects is exhausted. I can't seem to find any documentation explaining how to handle situations like these, which seems like a common one.
You can use Jackson's annotations for this (#JsonManagedReference and #JsonBackReference).
Check http://wiki.fasterxml.com/JacksonFeatureBiDirReferences
Or you can write your own serializer and keep track of what you've serialized already, which was what I ended up doing when I had to use Gson instead of Jackson.
I wrote detailed usage of jackson hibernate module (https://github.com/FasterXML/jackson-datatype-hibernate)
Sorry for my bad english.
As described below at OP post - if we use default jackson + jackson-databind modules - we had some troubles with many-to-many serialization. It's an "infinite cycle" if we don't use #JsonIgnore + #JsonIdentityInfo annotations. And it is "ID as result" if we use both of them.
Best way to solve troubles - use module.
1. Add maven dep
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>${jackson.version}</version>
</dependency>
Create and use custom ObjectMapper
HibernateAwareObjectMapper.java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
public class HibernateAwareObjectMapper extends ObjectMapper {
public HibernateAwareObjectMapper() {
registerModule(new Hibernate4Module());
}
}
3. Use this mapper in spring XML config
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="path.HibernateAwareObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
Add annotations to entities field.
Post entity don't need annotation.
Tag entity add #JsonIgnore annotation for fix "infinity loop" error.
Also we can change EAGER to LAZY fetchtype. And rule fetching by ObjectMapper config FORCE_LAZY_LOADING
More info in javadoc (http://fasterxml.github.io/jackson-datatype-hibernate/javadoc/h4-2.4/com/fasterxml/jackson/datatype/hibernate4/Hibernate4Module.Feature.html#FORCE_LAZY_LOADING) or code on github
Add two more annotations into your code:
#Entity
#Table(name = "POST")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "postId" )
public class Post implements Serializable {
#Id
#Column(name = "POST_ID")
#GeneratedValue(strategy-GenerationType.AUTO)
private Integer postId;
//Rest of the class
}
And:
#Entity
#Table(name = "TAG")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "tagId" )
public class Tag implements Serializable {
#Id
#Column(name = "TAG_ID")
#GeneratedValue(strategy-GenerationType.AUTO)
private Integer tagId;
//Rest of the class
}
As the Jackson documentation says:
In practice this is done by serializing the first instance as full object and object identity, and other references to the object as reference values.