Adding An entity manually in a JHipster project - jhipster

After introducing a custom UserType, I write a simple Entity class along with its Repository and a unit test class to test the UserType.
Here is the entity class:
#Entity
#Table(name = "person")
#TypeDef(name = "JsonDataUserType", typeClass = JsonDataUserType.class)
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
private String firstName;
private String lastName;
#Type(type = "JsonDataUserType")
private Map<String, String> additionalData;
...
}
}
And I create a new table person in the database.
Based on the JHipster Creating an entity document, I create a databaseChangeLog XML file with a name of time sequence
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<property name="now" value="now()" dbms="h2"/>
<property name="now" value="current_timestamp" dbms="postgresql"/>
<property name="floatType" value="float4" dbms="postgresql, h2"/>
<property name="floatType" value="float" dbms="mysql, oracle, mssql"/>
<changeSet id="20180508041900-1" author="jhipster">
<createTable tableName="person">
<column name="id" type="bigint" autoIncrement="${autoIncrement}">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="first_name" type="varchar(80)">
<constraints nullable="false" />
</column>
<column name="last_name" type="varchar(80)">
<constraints nullable="true" />
</column>
<column name="additional_data" type="jsonb">
<constraints nullable="false" />
</column>
<!-- jhipster-needle-liquibase-add-column - JHipster will add columns here, do not remove-->
</createTable>
</changeSet>
<!-- jhipster-needle-liquibase-add-changeset - JHipster will add changesets here, do not remove-->
The testes (gradlew test) fail. An error stack is something as the followings:
java.lang.IllegalStateException
Caused by: org.springframework.beans.factory.BeanCreationException
Caused by: javax.persistence.PersistenceException
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException
When I run the individual test, I get
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [person]
...
What is missing?

Related

Liferay JSF service builder configuration error

Am newbie to Liferay. I have created one JSF portlet and added the service builder to it. Using this link Generate a Persistence Framework. After configure a service builder i have generated the source using service builder. But am getting following exception when build service of project.
Exception in thread "main" java.lang.IllegalArgumentException: No entity column exist with column database name guestbookId for entity Guestbook
[echo] at com.liferay.portal.tools.service.builder.ServiceBuilder._getEntityColumnByColumnDBName(ServiceBuilder.java:4242)
[echo] at com.liferay.portal.tools.service.builder.ServiceBuilder._getColumnLengths(ServiceBuilder.java:3857)
[echo] at com.liferay.portal.tools.service.builder.ServiceBuilder._createSQLIndexes(ServiceBuilder.java:3355)
[echo] at com.liferay.portal.tools.service.builder.ServiceBuilder.<init>(ServiceBuilder.java:796)
[echo] at com.liferay.portal.tools.service.builder.ServiceBuilder.main(ServiceBuilder.java:216)
[echo] -Dservice.tpl.spring_xml_session=com/liferay/portal/tools/service/builder/dependencies/spring_xml_session.ftl
[echo] Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=512m; support was removed in 8.0
[mkdir] Created dir: D:\liferay\liferay-plugins-sdk-7.0\portlets\jsfguestbook-portlet\docroot\WEB-INF\service-classes
[copy] Copied 9 empty directories to 9 empty directories under D:\liferay\liferay-plugins-sdk-7.0\portlets\jsfguestbook-portlet\docroot\WEB-INF\service-classes
[javac] Compiling 38 source files to D:\liferay\liferay-plugins-sdk-7.0\portlets\jsfguestbook-portlet\docroot\WEB-INF\service-classes
[javac] Note: Some input files use or override a deprecated API.
[javac] Note: Recompile with -Xlint:deprecation for details.
[javac] Note: Some input files use unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
[jar] Building jar: D:\liferay\liferay-plugins-sdk-7.0\portlets\jsfguestbook-portlet\docroot\WEB-INF\lib\jsfguestbook-portlet-service.jar
[delete] Deleting directory D:\liferay\liferay-plugins-sdk-7.0\portlets\jsfguestbook-portlet\docroot\WEB-INF\service-classes
BUILD SUCCESSFUL
Total time: 8 seconds
Also after built the service i have added the code as per article. I got no method is configured for following code.
public Guestbook getFirstGuestbookByName(long groupId, String name) throws SystemException {
Guestbook guestbook = null;
List<Guestbook> guestbooks = guestbookPersistence.findByName(groupId, name); // This method is not exist.
if (guestbooks != null && guestbooks.size() > 0) {
guestbook = guestbooks.get(0);
}
return guestbook;
}
My service.xml configuration is follows
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 7.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_7_0_0.dtd">
<service-builder package-path="com.liferay.docs.guestbook">
<author>muthuvignesh.k</author>
<namespace>GB</namespace>
<entity name="Guestbook" local-service="true" uuid="true">
<!-- PK fields -->
<column name="guestbookId" type="long" primary="true"></column>
<!-- Group instance -->
<column name="groupId" type="long"></column>
<!-- Audit fields -->
<column name="companyId" type="long"></column>
<column name="userId" type="long"></column>
<column name="userName" type="String"></column>
<column name="createDate" type="Date"></column>
<column name="modifiedDate" type="Date"></column>
<column name="name" type="String"></column>
<finder name="GroupId" return-type="Collection">
<finder-column name="groupId"></finder-column>
</finder>
</entity>
<entity name="Entry" local-service="true" uuid="true">
<!-- PK fields -->
<column name="entryId" type="long" primary="true"></column>
<!-- Group instance -->
<column name="groupId" type="long"></column>
<!-- Audit fields -->
<column name="companyId" type="long"></column>
<column name="userId" type="long"></column>
<column name="userName" type="String"></column>
<column name="createDate" type="Date"></column>
<column name="modifiedDate" type="Date"></column>
<column name="name" type="String"></column>
<column name="email" type="String"></column>
<column name="message" type="String"></column>
<column name="guestbookId" type="long"></column>
<finder name="G_G" return-type="Collection">
<finder-column name="groupId"></finder-column>
<finder-column name="guestbookId"></finder-column>
</finder>
</entity>
<exceptions>
<exception>GuestbookName</exception>
<exception>EntryName</exception>
<exception>EntryMessage</exception>
<exception>EntryEmail</exception>
</exceptions>
Since i have using Liferay 7.0 for this. How to overcome this.
I had a same error in 6.2. It seems that the error was in the WEB-INF/sql/indexes.sql file. An index entry was using the field that the service builder could not find (but existed in the service.xml).
I think (but I couldn't verify it) the problem was occurred by an older version of liferay maven plugin (6.2.0-ga1). By deleting the file and upgrading to version 6.2.5, service builder generated a different indexes.sql and the build was successful.
Service Builder
The issue appears to be in your service builder.
1.What stands out the most is your malformed XML. You need to close the tag.
2.Secondly in your Java code you are trying to access a finder that doesn't exist. The only finder you have defined in your hibernate file is groupId. It would look something like
return guestbookPersistence.findByGroupId(groupId);
3.To create a groupid and name find it would look similar to your entry finder.
<finder name="G_N" return-type="Collection">
<finder-column name="groupId"></finder-column>
<finder-column name="name"></finder-column>
</finder>
and your java code
return guestbookPersistence.findByG_N(groupId, name);
service.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 7.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_7_0_0.dtd">
<service-builder package-path="com.latham.data">
<author>muthuvignesh.k</author>
<namespace>GB</namespace>
<entity name="Guestbook" local-service="true" uuid="true">
<!-- PK fields -->
<column name="guestbookId" type="long" primary="true"></column>
<!-- Group instance -->
<column name="groupId" type="long"></column>
<!-- Audit fields -->
<column name="companyId" type="long"></column>
<column name="userId" type="long"></column>
<column name="userName" type="String"></column>
<column name="createDate" type="Date"></column>
<column name="modifiedDate" type="Date"></column>
<column name="name" type="String"></column>
<finder name="GroupId" return-type="Collection">
<finder-column name="groupId"></finder-column>
</finder>
<finder name="G_N" return-type="Collection">
<finder-column name="groupId"></finder-column>
<finder-column name="name"></finder-column>
</finder>
</entity>
<entity name="Entry" local-service="true" uuid="true">
<!-- PK fields -->
<column name="entryId" type="long" primary="true"></column>
<!-- Group instance -->
<column name="groupId" type="long"></column>
<!-- Audit fields -->
<column name="companyId" type="long"></column>
<column name="userId" type="long"></column>
<column name="userName" type="String"></column>
<column name="createDate" type="Date"></column>
<column name="modifiedDate" type="Date"></column>
<column name="name" type="String"></column>
<column name="email" type="String"></column>
<column name="message" type="String"></column>
<column name="guestbookId" type="long"></column>
<finder name="G_G" return-type="Collection">
<finder-column name="groupId"></finder-column>
<finder-column name="guestbookId"></finder-column>
</finder>
</entity>
<exceptions>
<exception>GuestbookName</exception>
<exception>EntryName</exception>
<exception>EntryMessage</exception>
<exception>EntryEmail</exception>
</exceptions>
</service-builder>
The tutorial you linked to is for Liferay 6.2 but your document definition is for Liferay 7. There were some very big changes between 6.2 and 7. Your code seems to be compatible with both however you should still keep that in mind.

Liferay and relationships in it

I have a portlet, which can add/update/delete books and add authors. Moreover, you can choose existing authors when you try to add book.
And now I need to show how many books were written by each author in "author" table. How can I do it? Im a newbie in liferay and I even have no idea.
It's my service.xml
<entity name="Book" local-service="true" remote-service="true" cache-enabled="false">
<column name="bookId" type="long" primary="true" />
<column name="bookName" type="String" />
<column name="bookDescription" type="String" />
<column name="authors" type="Collection" entity="Author" mapping-table="Books_Authors" />
<finder return-type="Collection" name="bookName">
<finder-column name="bookName"></finder-column>
</finder>
</entity>
<entity name="Author" local-service="true" remote-service="true" cache-enabled="false">
<column name="authorId" type="long" primary="true" />
<column name="authorName" type="String" />
<column name="books" type="Collection" entity="Book" mapping-table="Books_Authors" />
</entity>
Service Builder is your friend.
You just need to add a finder in your book entity in service.xml. If your entity has a field named author:
<finder name="Author" return-type="Collection">
<finder-column name="author" />
</finder>
The execution of build-service will generate the methods BookUtil.findByAuthor() and BookUtil.countByAuthor().
You can implement now the corresponding methods in BookLocalServiceImpl, calling the previous, and after another run of build-serivce, they're available in your Util class. Something like
public List<Book> getByAuthor(String author) {
return getPersistence().findByAuthor(author);
}
public int countByAuthor(String author) {
return getPersistence().countByAuthor(author);
}
After the last call to build-service you can call them from your BookLocalServiceUtil.
If you just want the count, don't retrieve all the collection. If there are many records, it's a bad idea. Invoke the count instead.

Finder in Liferay's ServiceBuilder

I know I had already asked this question, but I have misunderstandings yet.
My previous question:
Liferay and relationships in it
In two words:
I have a portlet, which can add/update/delete books and add authors. Moreover, you can choose existing authors when you try to add book.
http://i.stack.imgur.com/vzUjn.png
And now I need to show how many books were written by each author in "author" table.
My service.xml:
<entity name="Book" local-service="true" remote-service="true" cache-enabled="false">
<column name="bookId" type="long" primary="true" />
<column name="bookName" type="String" />
<column name="bookDescription" type="String" />
<column name="authors" type="Collection" entity="Author" mapping-table="Books_Authors" />
<finder return-type="Collection" name="bookName">
<finder-column name="bookName"></finder-column>
</finder>
</entity>
<entity name="Author" local-service="true" remote-service="true" cache-enabled="false">
<column name="authorId" type="long" primary="true" />
<column name="authorName" type="String" />
<column name="books" type="Collection" entity="Book" mapping-table="Books_Authors" />
</entity>
What finder should I create to achieve my goal? If I create bookName finder Im able to count how many different books I have. If I create authorName finder Im able to count how many authors I have. Im a little lost.
Thank you for your help, but I still have some questions:
How and where can I get authorName with authorId?
How can I use my count variable in my table in view.jsp?
long count = BookLocalServiceUtil.countByAuthor(authorId);
public void addBook(ActionRequest actionRequest, ActionResponse actionResponse)
throws IOException, PortletException {
String bookName = ParamUtil.getString(actionRequest,"bookName");
String bookDescription = ParamUtil.getString(actionRequest, "bookDescription");
Long authorId = ParamUtil.getLong(actionRequest, "author");
try {
Book book = BookLocalServiceUtil.createBook(CounterLocalServiceUtil.increment());
book.setBookName(bookName);
book.setBookDescription(bookDescription);
book.setAuthorId(authorId);
book=BookLocalServiceUtil.addBook(book);
String author = ParamUtil.getString(actionRequest, "authorId");
} catch (Exception e) {
log.info(ADD_BOOK_ERROR, e);
SessionErrors.add(actionRequest, "PortalExceptionError");
}
}
In-case, if your Book can have only one Author (many-to-one), then following entity structure will work for you:
service.xml
<entity name="Book" local-service="true" remote-service="true" cache-enabled="false">
<column name="bookId" type="long" primary="true"></column>
<column name="bookName" type="String"></column>
<column name="bookDescription" type="String"></column>
<column name="authorId" type="long"></column>
<finder return-type="Collection" name="Author">
<finder-column name="authorId"></finder-column>
</finder>
</entity>
<entity name="Author" local-service="true" remote-service="true" cache-enabled="false">
<column name="authorId" type="long" primary="true"></column>
<column name="authorName" type="String"></column>
</entity>
On successful build, above finder will generate two methods findByAuthor(long authorId) and countByAuthor(long authorId) in BookUtil. Then, you can implement these methods in BookLocalServiceImpl as following:
BookLocalServiceImpl:
public List<Book> findByAuthor(long authorId) {
try {
return BookUtil.findByAuthor(authorId);
} catch (Exception ex) {}
return null;
}
public int countByAuthor(long authorId) {
try {
return BookUtil.countByAuthor(authorId);
} catch (Exception ex) {}
return 0;
}
And on building service once again, you can use these methods in your action class and on view from BookLocalServiceUtil.
Also, you are using JSTL on your views, you need to add dependencies of the jars in liferay-plugin-package.properties as following:
liferay-plugin-package.properties:
portal-dependency-jars=\
jstl-api.jar,\
jstl-impl.jar
Your Questions:
How and where can I get authorName with authorId?
<liferay-ui:search-container>
<liferay-ui:search-container-results results="${bookListArray}" />
<liferay-ui:search-container-row className="builder.model.Book"
keyProperty="bookId" modelVar="aBook">
<liferay-ui:search-container-column-text property="bookName"
name="book-Name" />
<liferay-ui:search-container-column-text property="bookDescription"
name="description" />
<%
Author bookAuthor = AuthorLocalServiceUtil.getAuthor(aBook.getAuthorId());
%>
<liferay-ui:search-container-column-text name="Author"
value="<%=bookAuthor.getAuthorName() %>" />
<liferay-ui:search-container-column-jsp path="/html/actionBook.jsp" />
</liferay-ui:search-container-row>
<liferay-ui:search-iterator />
</liferay-ui:search-container>
How can I use my count variable in my table in view.jsp?
<liferay-ui:search-container>
<liferay-ui:search-container-results results="${authorListArray}" />
<liferay-ui:search-container-row className="builder.model.Author"
keyProperty="authorId" modelVar="aAuthor">
<liferay-ui:search-container-column-text property="authorName"
name="author-Name" />
<%
int count = BookLocalServiceUtil.countByAuthor(aAuthor.getAuthorId());
%>
<liferay-ui:search-container-column-text name="count"
value="<%=String.valueOf(count) %>" />
<liferay-ui:search-container-column-jsp path="/html/actionAuthor.jsp" />
</liferay-ui:search-container-row>
<liferay-ui:search-iterator />
</liferay-ui:search-container>

NHibernate: Two foreign keys in the same table against same column

I am working on a consignment booking software.
The consignment object has the following structure:
public class Consignment
{
//Other properties
public virtual Station FromStation{get;set;}
public virtual Station ToStation{get;set;}
}
and here is the station object:
public class Station
{
public virtual int StationId{get;set;}
public virtual string StationName{get;set;}
//I don't want this property but I have to keep it.
public virtual Iesi.Collections.Generic.ISet<Consginment> ConsginmentFrom
{
get;
set;
}
//I don't want this property but I have to keep it here.
public virtual Iesi.Collections.Generic.ISet<Consginment> ConsginmentTo
{
get;
set;
}
}
In the database side, there would be a Station table, and the Consignment table would have two int columns (called FromStation and ToStation), storing the ids of the station.
I am not much of an NHibernate guy, after googling and reading for half a day I came up with the following Mapping Files:
Station.hbm.xml
<class name="Station" table="Station">
<id name="StationId" >
<column name="STATION_ID" not-null="true" />
<generator class="identity" />
</id>
<property name="StationName" >
<column name="STATION_NAME" not-null="true" />
</property>
<set name="ConsginmentFrom" inverse="true" lazy="true" generic="true">
<key>
<column name="StationId" />
</key>
<one-to-many class="Consignment" />
</set>
<set name="ConsignmentTo" inverse="true" lazy="false" generic="true">
<key>
<column name="StationId" />
</key>
<one-to-many class="Consignment" />
</set>
</class>
Consignment.hbm.xml
<class name="Consignment" abstract="true"
table="Consignment" lazy="false">
<id name="ConsignmentId">
<generator class="hilo"/>
</id>
<!--Column mapping for other properties-->
<many-to-one name="FromStation" class="Station">
<column name="STATION_ID" not-null="true" />
</many-to-one>
<many-to-one name="ToStation" class="Station">
<column name="STATION_ID" not-null="true" />
</many-to-one>
</class>
But the above is generating strange DB structure.
I have to do it xml mapping files as a lot has already been written in xml.
Am I doing it correctly? Can there be a better way?
Thanks for reading this.
There are a few things I can see wrong with the mappings.
The FromStation and ToStation properties map to the same column in the Consignment table. They should map to different columns such as FROM_STATION_ID and TO_STATION_ID:
<many-to-one name="FromStation" class="Station">
<column name="FROM_STATION_ID" not-null="true" />
</many-to-one>
<many-to-one name="ToStation" class="Station">
<column name="TO_STATION_ID" not-null="true" />
</many-to-one>
The Set for ConsignmentFrom and ConsignmentTo in Station maps to StationID instead of Station_Id. Also you need to use the FROM_STATION_ID AND TO_STATION_ID as the key columns.
<set name="ConsignmentFrom" inverse="true" lazy="true" generic="true">
<key column="FROM_STATION_ID" />
<one-to-many class="Consignment" />
</set>
<set name="ConsignmentTo" inverse="true" lazy="false" generic="true">
<key colum="TO_STATION_ID" />
<one-to-many class="Consignment" />
</set>
Also consignment is misspelt in some places which may also cause some confusion.

Linq for NHibernate strange Count() behavior

I have what strikes me as a very unusual behavior from Linq for NHibernate.
In general, all my entity classes are working just fine, but one of them throws a "NonUniqueResult" exception from the NHibernate namespace under the following condition.
If I call the following:
getSession<MyClass>().Linq<MyClass>().Count();
it throws the exception. If I call
getSession<MyClass>().Linq<MyClass>().ToList().Count();
it does not.
There's no problem with the other CRUD operations for this class, so I don't think it's my mappings.
My guess is that it has something to do with how the Count() operator ultimately gets materialized as a SQL query, but beyond that I'm not sure where to look.
Updated to include mapping of the class in question
<?xml version="1.0" encoding="utf-16"?>
<hibernate-mapping auto-import="true" default-lazy="false" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:nhibernate-mapping-2.2">
<class name="My.App.MyClass, My.App" table="MyClass">
<id name="Id" access="property" column="Id" type="System.Guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid.comb">
</generator>
</id>
<version name="TimeStamp" access="property" column="TimeStamp" type="Int32" />
<property name="CreatedOn" access="property" type="System.DateTime">
<column name="CreatedOn"/>
</property>
<property name="Ticker" access="property" type="Int32">
<column name="Ticker"/>
</property>
<property name="DifferentTicker" access="property" type="Int32">
<column name="DifferentTicker"/>
</property>
<property name="SomeDecimal" access="property" type="System.Decimal">
<column name="SomeDecimal"/>
</property>
<property name="StillAnotherTicker" access="property" type="Int32">
<column name="StillAnotherTicker"/>
</property>
<property name="IsActive" access="property" type="Boolean">
<column name="IsActive"/>
</property>
<many-to-one name="RelatedThing" access="property" class="My.App.RelatedThing, My.App" column="RelatedThingId" lazy="proxy" />
<many-to-one name="OtherRelatedThing" access="property" class="My.App.OtherRelatedThing, My.App" column="OtherRelatedThingId" lazy="proxy" />
<bag name="_schedule" access="property" table="Schedule" lazy="false" cascade="all-delete-orphan">
<key column="MyClassId" />
<one-to-many class="My.App.Schedule, My.App" />
</bag>
<bag name="Vectors" access="property" table="Vectors" lazy="false">
<key column="MyClassId" />
<many-to-many class="My.App.Channels.BaseVector, My.App" column="vectorid"/>
</bag>
</class>
</hibernate-mapping>
Both version work fine for me in a simple unit test. In this example:
using ( var tx = Session.BeginTransaction() )
{
int count = Session.Linq<Customer>().Count();
Assert.Equal( 2, count );
count = Session.Linq<Customer>().ToList().Count();
Assert.Equal( 2, count );
tx.Commit();
}
The first query performs a SQL count:
NHibernate: SELECT count(*) as y0_ FROM "Customer" this_
And the second gets all returns all items into a temporary list and then calls Count() on the list:
NHibernate: SELECT this_.Id as Id9_0_, this_.Name as Name9_0_ FROM "Customer" this_
This leads me to believe that there might be an issue with your mappings. And you really want to get the first one working cause the second won't scale for large datasets.

Resources