Liquibase diff does not work for #SecondaryTable - spring

If I add to my spring-boot / hibernate app an entity with secondary table, defined e.g. like this (taken from https://www.baeldung.com/jpa-mapping-single-entity-to-multiple-tables):
#Entity
#Table(name = "meal")
#SecondaryTable(name = "allergens", pkJoinColumns = #PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
Long id;
#Column(name = "name")
String name;
#Column(name = "description")
String description;
#Column(name = "price")
BigDecimal price;
#Embedded
Allergens allergens;
// standard getters and setters
}
#Embeddable
class Allergens {
#Column(name = "peanuts", table = "allergens")
boolean peanuts;
#Column(name = "celery", table = "allergens")
boolean celery;
#Column(name = "sesame_seeds", table = "allergens")
boolean sesameSeeds;
// standard getters and setters
}
and I run liquibase diff command to see what is the difference between entities in code and the underlying database (PostgreSQL or MySQL - it does not matter), I get only one change set:
<changeSet author="jakub (generated)" id="1585730629323-1">
<createTable tableName="meal">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="mealPK"/>
</column>
<column name="description" type="VARCHAR(255)"/>
<column name="name" type="VARCHAR(255)"/>
<column name="price" type="numeric(19, 2)"/>
</createTable>
</changeSet>
for table meal but nothing for table allergens!
It looks like Liquibase knows that allergens columns belong to another (secondary) table, but does not create changeSet for it. Is it a bug in Liquibase?
If I let Hibernate generate the tables, it correctly creates also secondary allergens table in database. If I now run diff command second time, it generates changeSet which is about to delete allergens table from database - that`s bad. Any ideas how to force Liquibase to recognize the secondary table?
My Maven profile used for running the diff is:
<profile>
<id>db-diff-postgresql</id>
<build>
<plugins>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.8.7</version>
<executions>
<execution>
<id>generate-db-diff-postgresql</id>
<phase>process-test-resources</phase>
<goals>
<goal>diff</goal>
</goals>
<configuration>
<propertyFile>src/main/resources/db/liquibase-postgresql.properties</propertyFile>
<diffChangeLogFile>src/main/resources/db/changelogs/postgresql/changelog_diff_postgresql_${maven.build.timestamp}.xml</diffChangeLogFile>
<logging>debug</logging>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
and liquibase-postgresql.properties is:
changeLogFile= src/main/resources/db/db.changelog.xml
driver= org.postgresql.Driver
url= jdbc:postgresql://localhost/myApp
username= postgres
password= password
referenceUrl= hibernate:spring:myapp.jpa.entity?dialect=org.hibernate.dialect.PostgreSQLDialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
Here: https://liquibase.jira.com/browse/CORE-3029 it seems to be the same problem, but not answered...
Thanks for any advice.

Related

Spring Boot - maven-jaxb2-plugin not generaing all classes - requests and responses for WSDL

We have an example WSDL that has been provided by an API provider and we want to integrate with it.
I tried the provided example at https://spring.io/guides/gs/consuming-web-service/ and some other .wsdl files and it all looks good.
In my case with my wsdl - when running the command to generate the classes - only some of them are generated, but not all of them.
This is not the case in SoapUI - all is good there.
Any info why this is happening?
My pom.xml is the following
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.2</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<generatePackage>com.test.xxx.soapclient.generated</generatePackage>
<generateDirectory>${project.basedir}/src/main/java</generateDirectory>
<schemaDirectory>${project.basedir}/src/main/resources/wsdl</schemaDirectory>
<schemaIncludes>
<include>*.wsdl</include>
</schemaIncludes>
</configuration>
</plugin>
</plugins>
</build>
What i have come to see is that only the complex types are being created as classes - while the others are not.
In my example the input message is the one below and no classes are being generated for it.
How can i do that?
Also what is interesting here is - soapAction has empty string as parameter - and Java's API requires SoapAction
Java Code
public Object callWebService(String action, Object request){
return getWebServiceTemplate().marshalSendAndReceive(request,new SoapActionCallback(action));
}
Actual WSDL File
<operation name="login" parameterOrder="user password">
<input message="tns:CardManagementEP_login"> </input>
<output message="tns:CardManagementEP_loginResponse"> </output>
</operation>
<message name="CardManagementEP_loginResponse">
<part name="result" type="xsd:string"> </part>
</message>
<message name="CardManagementEP_login">
<part name="user" type="xsd:string"> </part>
<part name="password" type="xsd:string"> </part>
</message>
<operation name="login">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal" namespace="http://com.tch.cards.service"/>
</input>
<output>
<soap:body use="literal" namespace="http://com.tch.cards.service"/>
</output>
</operation>
I managed to resolve the issue with the help of DaShaun Carter from the show Spring Office Hours.
The issue was that the above mentioned WSDL file was really old, so the classes for requests/responses which are not complex were not generated.
What we did is modify the existing WSDL and create these things as complex types - so Jaxb will generate classes for them
example-wsdl.wsdl
<complexType name="login">
<sequence>
<element name="user" type="string"/>
<element name="password" type="string"/>
</sequence>
</complexType>
<complexType name="loginResponse">
<sequence>
<element name="result" type="string"/>
</sequence>
</complexType>
After that, the classes are getting generated, but they did not work for me, and i had to some manual changes in order to get them working
LoginResponse.java
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "loginResponse", namespace = "http://com.tch.cards.service")
#XmlAccessorType(XmlAccessType.FIELD)
public class LoginResponse {
#XmlElement(required = true)
protected String result;
public String getResult() {
return result;
}
public void setResult(String value) {
this.result = value;
}
}
Login
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "login", namespace = "http://com.tch.cards.service")
#XmlAccessorType(XmlAccessType.FIELD)
public class Login {
#XmlElement(required = true)
protected String user;
#XmlElement(required = true)
protected String password;
public String getUser() {
return user;
}
public void setUser(String value) {
this.user = value;
}
public String getPassword() {
return password;
}
public void setPassword(String value) {
this.password = value;
}
}
Also, in my case, the Soap Action did not matter, and i am passing empty strings.
The code where the actual calls were happening is as follows:
Login login = new Login();
login.setUser("user");
login.setPassword("password");
LoginResponse response = (LoginResponse) soapConnector.callWebService("", login);
System.out.println(response);
IMPORTANT NOTE: Change the namespace as per usecase - this is really important

Hibernate #LazyToOne(LazyToOneOption.NO_PROXY) #OneToOne(fetch = FetchType.LAZY) always fetched eagerly

My goal is to enable lazy loading for a bidirectional ono-to-one association on the parent-side of the association. Consider the following two entities:
Parent Vendor.java:
#Entity
#Audited
#NoArgsConstructor
#Getter
public class Vendor extends User {
#JoinColumn(nullable = false, unique = true, updatable = false)
#LazyToOne(LazyToOneOption.NO_PROXY)
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "vendor", optional = false, orphanRemoval = true)
private Operator operator;
}
Child Operator.java:
#Entity
#Audited
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class Operator {
#Id
private Long id;
#JoinColumn(name = "id")
#MapsId
#OneToOne(fetch = FetchType.LAZY, optional = false)
private Vendor vendor;
}
Note: I omitted other fields, constructors and equals/hashCode functions for brevity.
As mentioned before, my goal is to load the vendor's operator lazyly. Therefore following the Hibernate user guide and Vlad Mihalcea's tutorial on how to enable bytecode enhancement in Hibernate, I annotated the parent-side association with #LazyToOne(LazyToOneOption.NO_PROXY) in addtion to the #OneToOne with fetch = FetchType.LAZY. After adding the following plugin to my pom.xml, the operator is still loaded eagerly upon fetching the vendor.
Plugin configuration:
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
<execution>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
When I fetch a vendor, the following SQL is generated:
2019-10-18 13:43:16.138 INFO 12212 --- [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:8, Time:2, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select vendor0_.id as id1_39_0_, vendor0_1_.created_at as created_2_39_0_, vendor0_1_.version as version3_39_0_, vendor0_1_.account_non_locked as account_4_39_0_, vendor0_1_.email as email5_39_0_, vendor0_1_.email_verified as email_ve6_39_0_, vendor0_1_.enabled as enabled7_39_0_, vendor0_1_.last_password_reset_date as last_pas8_39_0_, vendor0_1_.password as password9_39_0_, vendor0_.address_district as address_1_43_0_, vendor0_.address_latitude as address_2_43_0_, vendor0_.address_longitude as address_3_43_0_, vendor0_.address_city as address_4_43_0_, vendor0_.address_country_code as address_5_43_0_, vendor0_.address_street as address_6_43_0_, vendor0_.address_street_no as address_7_43_0_, vendor0_.address_zip_code as address_8_43_0_, vendor0_.contact_details_email as contact_9_43_0_, vendor0_.contact_details_phone as contact10_43_0_, vendor0_.customer_no_customer_no as custome17_43_0_, vendor0_.description as descrip11_43_0_, vendor0_.hidden as hidden12_43_0_, vendor0_.name as name13_43_0_, vendor0_.slug as slug14_43_0_, vendor0_.verified as verifie15_43_0_ from vendor vendor0_ inner join users vendor0_1_ on vendor0_.id=vendor0_1_.id where vendor0_.id=?"]
Params:[(4)]
2019-10-18 13:43:16.141 INFO 12212 --- [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:8, Time:1, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select operator0_.id as id1_17_0_, operator0_.address_city as address_2_17_0_, operator0_.address_country_code as address_3_17_0_, operator0_.address_street as address_4_17_0_, operator0_.address_street_no as address_5_17_0_, operator0_.address_zip_code as address_6_17_0_, operator0_.bank_account_bic as bank_acc7_17_0_, operator0_.bank_account_iban as bank_acc8_17_0_, operator0_.bank_account_owner as bank_acc9_17_0_, operator0_.company as company10_17_0_, operator0_.contact_details_email as contact11_17_0_, operator0_.contact_details_phone as contact12_17_0_, operator0_.contact_person_first_name as contact13_17_0_, operator0_.contact_person_gender as contact14_17_0_, operator0_.contact_person_last_name as contact15_17_0_, operator0_.tax_id as tax_id16_17_0_, operator0_.vat_id as vat_id17_17_0_ from operator operator0_ where operator0_.id=?"]
Params:[(4)]
When I comment out the operator field in Vendor.java, only the first statement is generated. I also tried setting the spring.jpa.properties.hibernate.ejb.use_class_enhancer property as suggested in this answer, but it didn't change the generated SQL. My apprehension is that this issue might be related to the mapping with #MapsId in Operator.java as this is not used in the Hibernate example.
Version of used frameworks:
Spring Boot 2.1.9.RELEASE
Hibernate 5.3.12.Final
My mistake was to place the hibernate-enhance-maven-plugin to early in the plugin list of the <build> part in the pom.xml, namely before the maven-compiler-plugin. After placing it at the end of the <plugins> list, it worked as expected.

LocalDateTime mapped to Oracle DATE, but not to H2 DATE

Let's say I have JPA #Embeddable:
#Embeddable
public class SpecificationValidity {
#Column(name = "VALID_FROM", nullable = false)
private final LocalDateTime validFrom;
#Column(name = "VALID_TO")
private final LocalDateTime validTo;
}
SQL table contains columns VALID_FROM and VALID_TO and is declared using liquibase changeset as follows:
<column name="VALID_FROM" type="date">
<constraints nullable="false"/>
</column>
<column name="VALID_TO" type="date"/>
When I run this code against Oracle database, everything works.
When I run it against H2 database, Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: wrong column type encountered in column [valid_from] in table [specification]; found [date (Types#DATE)], but expecting [timestamp (Types#TIMESTAMP)]
Why is it?
Is it possible to have consistent mapping for both dbms?
I assume you use Hibernate(from your exception message). Since you are using java 8 or above, you might need to add this to your dependency for hibernate 5.0.x.
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-java8 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>5.3.7.Final</version>
</dependency>
This helps to convert to and fro from Java 8 types to JPA known types. In this case it allows LocalDateTime LocalDate and Instant etc.,
I'm putting out the mapping that comes along(referred in the article as well).
A reference article : Hibernate + java 8 datetime
P.S : For Hibernate 5.2x and above no need for this explicit dependency.
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#basic-datetime

#generatedvalue not working, still asking to manually assign id

I'm learning spring-hibernate and trying to get my web service working, but now i'm stuck with this situation. My service for getting the data work well but when i try to post this condition happen :
i already use #generated value annotation
trying modif it's type like : GenerationType.SEQUENCE, IDENTITY, etc (none of them work either)
So far my best guess, so this is my code
This is my model class (part of it) the other is typical like email, phone, etc. And it dont have any relation to another table, only 1 table on my DB btw i use ms sql server
#Entity
#Table(name="Patient")
#SuppressWarnings("serial")
public class Patient implements Serializable {
#Id
#GenericGenerator(name = "guidGenerator", strategy = "guid")
#GeneratedValue( generator= "guidGenerator")
#Column(name = "PatientGUID", columnDefinition="uniqueidentifier")
private String patientGuid;
private String firstName;
private String lastName;
public String getPatientGuid() {
return patientGuid;
}
public void setPatientGuid(String patientGuid) {
this.patientGuid = patientGuid;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Edit : My hbm file
<hibernate-mapping package="training.model">
<class name="Patient" table="Patient">
<id name="patientGuid" type="string">
<column name="PatientGUID" />
<generator class="assigned" />
</id>
<property name="practiceGuid" type="string">
<column name="PracticeGUID" sql-type="varchar" length="38" not-null="true" />
</property>
<property name="firstName" type="string">
<column name="FirstName" sql-type="varchar" length="30" not-null="true" />
</property>
<property name="lastName" type="string">
<column name="LastName" sql-type="varchar" length="30" not-null="true" />
</property>
</class>
</hibernate-mapping>
My table detail which i believed caused this problim is : PatientGUID and it's type is UNIQUEIDENTIFIER, when i execute my webservice and request POST method to post my patient i got an error, which is :
ids for this class must be manually assigned before calling save()
So guys i really need help, what did i do wrong or what did i miss? If you need any more detail on my code just leave a comment, thanks
EDIT : The ERROR Message
Caused by: org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): training.model.Patient
at org.hibernate.id.Assigned.generate(Assigned.java:52)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:117)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:206)
at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:55)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:191)
at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:49)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:764)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:756)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:752)
at training.dao.PatientDaoImpl.savePatient(PatientDaoImpl.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.sun.proxy.$Proxy20.savePatient(Unknown Source)
at training.service.PatientServiceImpl.addPatient(PatientServiceImpl.java:29)
at training.ws.PatientWS.addPatients(PatientWS.java:110)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:180)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96)
There is no guid strategy in hibernate, there is however uuid. Following should work.
#GenericGenerator(name = "guidGenerator", strategy = "uuid2")

Hibernate :many to many doesn't work

I'm working on a many to many relationship with hibernate
here are my tables in the data-base :
table : Service
table : Pays
and the join table : service-pays
I have 2 entities : Service and Pays
here is my code:
public class Service implements java.io.Serializable {
private Integer idService;
private String nomService;
private Set<Pays> payses = new HashSet<Pays>();
// getters & setters
....
}
public class Pays implements java.io.Serializable {
private Integer idPays;
private String nomPays;
private Set<Service> services = new HashSet<Service>(0);
// getters & setters
....
}
the mapping fils are:
<hibernate-mapping package="pckg">
<class name="pckg.Service" dynamic-update="true" select-before-update="true" table="service" catalog="database" lazy="false" >
<id name="idService" type="java.lang.Integer">
<column name="id_service" />
<generator class="identity" />
</id>
<property name="nomService" />
<set name="countries" table="service_pays" lazy="true"
inverse="true" cascade="save-update">
<key column="id_service" />
<many-to-many column="id_pays" class="pckg.Pays" />
</set>
</class>
</hibernate-mapping>
<hibernate-mapping package="pckg">
<class name="pckg.Pays" dynamic-update="true" select-before-update="true" table="service" catalog="database" lazy="false" >
<id name="idPays" type="java.lang.Integer">
<column name="id_pays" />
<generator class="identity" />
</id>
<property name="nomPays" />
<set name="ser" table="service_pays" lazy="true"
cascade="save-update" >
<key column="id_pays" />
<many-to-many column="id_service" class="pckg.Service" />
</set>
</class>
</hibernate-mapping>
my methode to save the service:
Pays p1= new Pays("France");
Pays p2=new Pays("Italy");
Set<Pays> list-pays=new HashSet<Pays>();
list-pays.add(p1);
list-pays.add(p2);
Service service=new Service();
service.setNomService("nomService");
service.setCountries(list-pays);
serviceBo.saveService(service);
Here is serviceBo.saveService:
public class ServiceBoImp implements ServiceBo ,Serializable{
ServiceDao serviceDao;
//getter & setter of serviceDao
#Override
public void saveService(Service sited) {
serviceDao.saveService(sited);
}
}
and here is ServiceDao
public class ServiceDaoImp extends HibernateDaoSupport implements ServiceDao ,Serializable {
#Override
public void saveService(ServiceDsite s) {
getHibernateTemplate().setCheckWriteOperations(false);
getHibernateTemplate().save(s);
}
}
the problem is that when i save a service with its Set ,the service is saved correctly but without Pays which means the join table is always empty
I think the problem is that service.getPays() is marked with inverse="true". This means that Hibernate should ignore this side of the relation and track only the other.
Try to move the inverse=true to the other side of the relation, and then the cascade save should work.

Resources