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.
Related
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?
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.
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>
I have created custom entity employee. Now I want to add listeners for this entity so that I can track add/edit/delete employee operations.
In Liferay for portal enitites like Blogs, Group, User etc we can add properties like
value.object.listener.com.liferay.portal.model.Group=com.smb.test.hook.listeners.GroupListener
in portal.properties via hook-plugin.
But for custom entity this approach does not seem to work.
Any help would be appreciated.
For custom entity, we need to add the listener property in service-ext.properties instead of the portal.properties file.
For my employee entity I have added following property in service-ext.properties file:
value.object.listener.com.smb.employee.model.Employee=com.smb.employee.hook.listeners.EmployeeListener
Note: We need to manually create service-ext.properties file in src folder besides the service.properties file. We could have updated service.properties but since it auto-generates our changes would be lost and hence service-ext.properties is the correct liferay approach.
Here is the location of the service-ext.properties file:
Here is my service.xml:
<service-builder package-path="com.smb.employee">
<author>Suyash</author>
<namespace>smb</namespace>
<entity name="Employee" local-service="true" remote-service="true">
<!-- PK fields -->
<column name="fooId" type="long" primary="true" />
<!-- Audit fields -->
<column name="companyId" type="long" />
<column name="userId" type="long" />
<column name="userName" type="String" />
<column name="createDate" type="Date" />
<column name="modifiedDate" type="Date" />
<!-- Other fields -->
<column name="field1" type="String" />
<column name="field2" type="boolean" />
<column name="field3" type="int" />
<column name="field4" type="Date" />
<column name="field5" type="String" />
<!-- Order -->
<order by="asc">
<order-column name="field1" />
</order>
<!-- Finder methods -->
<finder name="Field2" return-type="Collection">
<finder-column name="field2" />
</finder>
</entity>
</entity>
</service-builder>
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.