ATG - One to Many mapping and list property results - sql

I am trying to do some I supposed to be very simple ... but I have some problems.
TABLE PROVINCE:
CODISTPROV NAME SIGPROV
1 MILAN MI
2 ROME RM
3 NAPLES NA
and TABLE COMUNI:
CODISTPROV CODISTCOM DESC_COM
1 1 XX1
1 2 XX2
2 3 YY3
2 4 YY4
3 5 ZZ5
where CODISTPROV in COMUNI is FOREIGN KEY TO PROVINCE.
The xml is
<item-descriptor name="comuniWithSiglaProvincia" writable="false" item-cache-timeout="86400000" item-expire-timeout="604800000">
<table name="province" type="primary" id-column-names="codistprov">
<property name="sigla_provincia" data-type="String" column-name="sigprov" display-name-resource="Sigla provincia"/>
</table>
<table name="comuni" type="multi" id-column-names="codistprov,codistcom" multi-column-name="codistcom">
<property name="listaComuni" data-type="list" component-data-type="String" column-name="desc_com" />
</table>
</item-descriptor>
I'd like to implement this query
SELECT C.DESC_COM,P.SIGPROV
FROM COMUNI C, PROVINCE P
WHERE C.DESC_COM LIKE 'PAR%' AND C.CODISTPROV = P.CODISTPROV;
where PAR are some characters from input.
The code:
RepositoryView view = getTopoAnagraficaRepository().getView("comuniWithSiglaProvincia");
Object params[] = new Object[1];
params[0] = comuneInitialCharacters;
QueryBuilder repositoryBuilder = view.getQueryBuilder();
QueryExpression prop = repositoryBuilder.createPropertyQueryExpression(PROPERTY_LISTA_COMUNI);
QueryExpression value = repositoryBuilder.createConstantQueryExpression(new String(comuneInitialCharacters));
Query query = repositoryBuilder.createPatternMatchQuery(prop, value, QueryBuilder.STARTS_WITH);
listRepItem = view.executeQuery(query); // 19 results
if(listRepItem != null){
for (RepositoryItem item : listRepItem) {
System.out.println("PROV=" + item.getPropertyValue("sigla_provincia")); // it prints correct value
if (item.getPropertyValue("listaComuni") != null) // ArrayIndexOutOfBoundException
[...]
The query return 19 listRepItem (it is correct) but I got ArrayIndexOutOfBoundException in the last line above, when I try to access the sublist.
Any hints?
What is the correct way to access a property value which is a list?
For example with the data above, if input characters were 'XX' I should have as results
XX1 MI
XX2 MI
Thanks

So what I believe you are trying to achieve is represent the below data model in a Repository as follows:
|
|-- Country1
| |
| |------- Province1
| |------- Province2
| |------- Province3
|-- Country2
|
|------- Province4
|------- Province5
|------- Province6
To achieve this in the repository definition you should probably use a set instead of a list. The documentation states:
The multi-column-name attribute ensures that the ordering of the multi-values are maintained. The column specified by the multi-column-name attribute is used for multi-valued properties of data type array, map, and list and is not used for sets (which are unordered). For map type properties, the values in the column specifiedy by the multi-column-name attribute must be a string. For list or array type properties, these values should be an integer or numeric type, and must be sequential.
To achieve this your repository definition for Country would look something like this:
<item-descriptor display-name-resource="Country" use-id-for-path="false" content="false" writable="true" default="true" display-property="name" folder="false" cache-mode="simple" id-separator=":" name="country" >
<table shared-table-sequence="1" name="COUNTRY" id-column-name="id" type="primary">
<property readable="true" queryable="true" hidden="false" backing-map-property="false" name="id" column-name="ID" data-type="string" required="true" writable="true"/>
<property readable="true" queryable="true" display-name="Name" cache-mode="inherit" backing-map-property="false" name="name" column-name="DISPLAY_NAME" data-type="string" required="true" writable="true"/>
</table>
<table shared-table-sequence="1" name="COUNTRY_PROVINCE" id-column-names="COUNTRY_ID" type="multi">
<property readable="true" display-name-resource="provinces" data-type="set" component-item-type="province" required="false" writable="true" queryable="true" cache-mode="inherit" backing-map-property="false" name="provinces" column-name="PROVINCE_ID"/>
</table>
</item-descriptor>
The province repository definition would look something like this:
<item-descriptor display-name-resource="Province" default="false" expert="false" display-property="name" folder="false" id-separator=":" name="province" use-id-for-path="false" content="false" writable="true">
<table shared-table-sequence="1" name="Province" id-column-name="id" type="primary">
<property readable="true" queryable="true" expert="false" hidden="false" cache-mode="inherit" backing-map-property="false" name="id" column-name="ID" data-type="string" required="true" writable="true"/>
<property readable="true" queryable="true" expert="false" hidden="false" display-name="Name" cache-mode="inherit" backing-map-property="false" name="name" column-name="DISPLAY_NAME" data-type="string" required="true" writable="true"/>
</table>
</item-descriptor>
More information can be found in the Oracle Commerce Documentation.

Related

Using ValueDataSource to show skalar value

I want to show a skalar values by using a label. The following example shows the sum of customer order amounts.
https://doc.cuba-platform.com/manual-6.8/value_datasources.html
<dsContext>
<valueCollectionDatasource id="salesDs">
<query>
<![CDATA[select o.customer, sum(o.amount) from demo$Order o group by o.customer]]>
</query>
<properties>
<property class="com.company.demo.entity.Customer" name="customer"/>
<property datatype="decimal" name="sum"/>
</properties>
</valueCollectionDatasource>
here I want to bind the loaded data to the label:
<label datasource="salesDs"
property="name"/>
but nothing is shown.
Why is the label value empty? (salesDs is correctly loaded, I can step through with intelliJ...)
I also tried to get the loaded data from the datasource, but I can't find the correct way.
salesDs.getItem() //returns null
salesDs.getItems() //retruns a collection of KeyValueEntries
But, what is the correct way to get my data from a KeyValueEntry?
It would work if you selected an item in the collection datasource (no matter ValueCollectionDatasource or a normal one). It can be done programmatically via setItem() or by some visual component also connected to the same datasource.
For example, in the below screen the label shows the name of the customer currently selected in the table:
<dsContext>
<valueCollectionDatasource id="customersDs">
<query>
<![CDATA[select e.name, e.email
from sales$Customer e]]>
</query>
<properties>
<property datatype="string"
name="name"/>
<property datatype="string"
name="email"/>
</properties>
</valueCollectionDatasource>
</dsContext>
<layout expand="customersTable" spacing="true">
<table id="customersTable" width="100%">
<columns>
<column id="name"/>
<column id="email"/>
</columns>
<rows datasource="customersDs"/>
</table>
<groupBox caption="Label">
<label id="nameLab"
datasource="customersDs"
property="name"/>
</groupBox>
</layout>

coldfusion ORM: many-to-many conditional property

I am trying to add a where clause to a many-to-many property I have defined in one of my objects. I must be doing something wrong though because I keep getting hibernate errors saying that the column doesn't exist.
in the template cfc:
<cfproperty
name="Settings"
fieldtype="many-to-many"
cfc="Setting"
linktable="settings_templates"
fkcolumn="templateID"
inversejoincolumn="settingsId"
where="deleted='false'"
>
In the settings cfc:
<cfproperty
name="templates"
fieldtype="many-to-many"
cfc="Template"
linktable="settings_templates"
fkcolumn="settingsId"
inversejoincolumn="templateID"
where="deleted='false'"
>
The error I am getting is:
08/02 16:06:27 [jrpp-170] HIBERNATE ERROR - [Macromedia][SQLServer
JDBC Driver][SQLServer]Invalid column name 'deleted'.
Can anyone see what I am doing wrong? there is a deleted column in both tables, but not in the link table.
The where property behavior for many-to-many is very strange...
In order to debug this, activate the Hibernate logging is primordial.
Refer you to this post: http://www.rupeshk.org/blog/index.php/2009/07/coldfusion-orm-how-to-log-sql/
Take this example:
Article.cfc
/**
* #output false
* #persistent true
* #table article
*/
component {
property name="id" fieldtype="id";
property name="title";
property
name="tags" singularname="tag"
fieldtype="many-to-many" cfc="Tag" linktable="link_article_tag" fkcolumn="articleId"
inversejoincolumn="tagId" where=" deleted = 0 "
;
}
Tag.cfc
/**
* #output false
* #persistent true
* #table tag
*/
component {
property name="id" fieldtype="id";
property name="name";
property name="deleted" dbdefault="0";
property
name="articles" singularname="article"
fieldtype="many-to-many" cfc="Article" linktable="link_article_tag" fkcolumn="tagId"
inversejoincolumn="articleId"
;
}
When I try to list all articles like this ormExecuteQuery('from Article'), see what is appened in the hibernate logs:
select
tags0_.articleId as articleId6_1_,
tags0_.tagId as tagId1_,
tag1_.id as id0_0_,
tag1_.name as name0_0_,
tag1_.deleted as deleted0_0_
from
link_article_tag tags0_
inner join
tag tag1_
on tags0_.tagId=tag1_.id
where
tags0_.deleted = 0
Damned! WTF :| tags0_ == join table ... You see what's wrong?
In order to understand this behavior, I'm going to activate HBMXML generation in Application.cfc with this.ormSettings.saveMapping = true
Here is the files:
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class entity-name="Tag" lazy="true"
name="cfc:www.app.models.test.Tag" table="tag">
<id name="id" type="string">
<column length="255" name="id"/>
</id>
<property name="name" type="string">
<column name="name"/>
</property>
<property name="deleted" type="string">
<column default="0" name="deleted"/>
</property>
<bag name="articles" table="link_article_tag">
<key column="tagId"/>
<many-to-many class="cfc:www.app.models.test.Article" column="articleId"/>
</bag>
</class>
</hibernate-mapping>
What we can see: The where attribute is set at the bag level, not at the to many-to-many. If I edit like this:
<bag name="tags" table="link_article_tag">
<key column="articleId"/>
<many-to-many class="cfc:www.app.models.test.Tag" column="tagId" where=" deleted = 0 "/>
</bag>
The generated SQL become:
select
tags0_.articleId as articleId62_1_,
tags0_.tagId as tagId1_,
tag1_.id as id59_0_,
tag1_.name as name59_0_,
tag1_.deleted as deleted59_0_
from
link_article_tag tags0_
inner join
tag tag1_
on tags0_.tagId=tag1_.id
where
tag1_.deleted = 0
This method works but is not good for code maintenance and readability.
If anyone has a better solution, I'm interested.
So far, the only way around this that I have found is to override the getter using ORMExecuteQuery(). There I can query the objects directly and look for things such as this. Personally, I prefer to work in cfscript, so my code would look something like this:
public array function getSettings() {
return ORMExecuteQuery("SELECT s FROM template t JOIN t.settings s WHERE t.id=:id AND s.deleted=:deleted", {
deleted=false, id=this.getId()
});
}
N.B. – EXAMPLE NOT TESTED
Of course, you don't have to use the parameters as I have, you could just use deleted=false, but that's up to you (I like using parameters, personally).
Also, don't forget the singularName attribute on many-to-many properties.
On a related note, I've had issues using properties on both objects. You really only need to use them on the template object, in this case. Think about it this way: A template needs to know its settings, but the settings don't need to know about the template they belong to – using properties on both objects in this way can lead to errors, in some cases.

Hibernate can't load Custom SQL collection

There is a table Item like,
code,name
01,parent1
02,parent2
0101,child11
0102,child12
0201,child21
0202,child22
Create a java object and hbm xml to map the table.The Item.parent is a Item whose code is equal to the first two characters of its code :
class Item{
String code;
String name;
Item parent;
List<Item> children;
.... setter/getter....
}
<hibernate-mapping>
<class name="Item" table="Item">
<id name="code" length="4" type="string">
<generator class="assigned" />
</id>
<property name="name" column="name" length="50" not-null="true" />
<many-to-one name="parent" class="Item" not-found="ignore">
<formula>
<![CDATA[
(select i.code,r.name from Item i where (case length(code) when 4 then i.code=SUBSTRING(code,1,2) else false end))
]]>
</formula>
</many-to-one>
<bag name="children"></bag>
</class>
</hibernate-mapping>
I try to use formula to define the many-to-one relationship,but it doesn't work!Is there something wrong?Or is there other method?
Thanks!
ps,I use mysql database.
add 2010/05/23
Pascal's answer is right,but the "false" value must be replaced with other expression,like "1=2".Because the "false" value would be considered to be a column of the table.
select i.code
from Item i
where (
case length(code)
when 4 then i.code=SUBSTRING(code,1,2)
else 1=2
end)
And I have another question about the children "bag" mapping.There isn't formula configuration option for "bag",but we can use "loader" to load a sql-query.I configure the "bag" as following.But it get a list whose size is 0.What's wrong with it?
<class>
... ...
<bag name="children">
<key />
<one-to-many class="Item"></one-to-many>
<loader query-ref="getChildren"></loader>
</bag>
</class>
<sql-query name="getChildren">
<load-collection alias="r" role="Item.children" />
<![CDATA[(select {r.*} from Item r join Item o where
o.code=:code and
(
case length(o.code)
when 2 then (length(r.code)=4 and SUBSTRING(r.code,1,2)=o.code)
else 1=2
end ))]]>
</sql-query>
According to the documentation, you're supposed to return the value of the computed foreign key, nothing more:
formula (optional): an SQL expression that defines the value for a computed foreign key.
So I would expect a query like this:
select i.code
from Item i
where (
case length(code)
when 4 then i.code=SUBSTRING(code,1,2)
else false
end)
Disclaimer: not tested.

Hibernate - New colums from a joined table [duplicate]

This question already has answers here:
Hibernate One-To-Many Unidirectional on an existing DB
(4 answers)
Closed 2 years ago.
I have a class User object. I am trying to load this object from the Database using Hibernate.
My SQL statement is:
SELECT
users.uid AS uid,
users.deleted AS deleted,
users.username AS username,
users.passwd AS passwd,
users.disabled AS disabled,
users.lockout AS lockout,
users.expires AS expires,
data_firstname.value AS firstname,
data_lastname.value AS lastname
FROM
ac_users as users
LEFT JOIN
ac_userdef_data as data_firstname
ON
users.uid = data_firstname.parentuid AND
data_firstname.fieldname like 'firstname'
LEFT JOIN
ac_userdef_data as data_lastname
ON
users.uid = data_lastname.parentuid AND
data_lastname.fieldname like 'lastname'
WHERE
users.deleted = 0
My mapping for the User class is:
<class name="com.agetor.commons.security.User" table="ac_users">
<id name="uid" column="uid" type="long" >
<generator class="native"/>
</id>
<property name="deleted" column="deleted" />
<property name="username" column="username" />
<property name="password" column="passwd" />
<property name="firstname" column="firstname" />
<property name="lastname" column="lastname" />
<property name="disabled" column="disabled" />
<property name="lockout" column="lockout" />
<property name="expires" column="expires" />
</class>
My problem is that the table ac_users does not have a column 'firstname' or 'lastname'
These columns, only exist in my resultset from the SQL-join statement.
They do also not exist in the ac_userdef_data table. There i have 2 colums: fieldname and value. and 2 rows:
fieldname = 'firstname' with some value in the value column
and another row with
fieldname = 'lastname' with some value in the value column
How do i change my mapping file, so that Hibernate will understand that it needs to load the firstname and lastname column into my firstname and lastname fields on my POJO, while those columns dont actually exist on the referenced ac_users table.?
I now have some working code. The trick was to specify a loading Query for the User class. Hibernate then validates against the return from the Query instead of the Table design.
Class mapping
<class name="com.agetor.commons.security.User" table="ac_users">
<id name="uid" column="uid" type="long" >
<generator class="native"/>
</id>
<property name="deleted" column="deleted" />
<property name="username" column="username" />
<property name="password" column="passwd" />
<property name="firstname" column="firstname" />
<property name="lastname" column="lastname" />
<property name="disabled" column="disabled" />
<property name="lockout" column="lockout" />
<property name="expires" column="expires" />
<loader query-ref="GetAllUsers" />
</class>
My Query declaration
<sql-query name="GetAllUsers">
<return alias="user" class="com.agetor.commons.security.User" />
<![CDATA[
SELECT
users.uid AS uid,
users.deleted AS deleted,
users.username AS username,
users.passwd AS passwd,
users.disabled AS disabled,
users.lockout AS lockout,
users.expires AS expires,
data_firstname.value AS firstname,
data_lastname.value AS lastname
FROM
ac_users as users
LEFT JOIN
ac_userdef_data as data_firstname
ON
users.uid = data_firstname.parentuid AND
data_firstname.fieldname like 'firstname'
LEFT JOIN
ac_userdef_data as data_lastname
ON
users.uid = data_lastname.parentuid AND
data_lastname.fieldname like 'lastname'
WHERE
users.deleted = 0
]]>
</sql-query>
I had to use the line <return alias="user" class="com.agetor.commons.security.User" /> so that the returned collection was typed correctly

where clause as a parameter

IN converting over a legacy application we need to convert named query to nhibernate. The problem is that the where clause is being set.
here is the mapping
<resultset name="PersonSet">
<return alias="person" class="Person">
<return-property column="id" name="Id" />
<return-property column="ssn" name="Ssn" />
<return-property column="last_name" name="LastName" />
<return-property column="first_name" name="FirstName"/>
<return-property column="middle_name" name="MiddleName" />
</return>
</returnset>
<sql-query name="PersonQuery" resultset-ref="PersonSet" read-only="true" >
<![CDATA[
SELECT
person.ID as {person.Id},
person.SSN as {person.Ssn},
person.LAST_NAME as {person.LastName},
person.MIDDLE_NAME as {person.MiddleName},
person.FIRST_NAME as {person.FirstName},
FROM PERSONS as person
where :value
]]>
</sql-query>
and the c# code:
String query = "person.LAST_NAME = 'Johnson'";
HibernateTemplate.FindByNamedQueryAndNamedParam("PersonQuery", "value", query);
The error:
where ?]; ErrorCode []; An expression of non-boolean type specified in a context where a condition is expected, near '#p0'.
This doesn't work because you try to replace :value with "person.LAST_NAME = 'Johnson'" wanting that the query becomes
SELECT person.ID, person.SSN, person.LAST_NAME, person.MIDDLE_NAME, person.FIRST_NAME
FROM PERSONS as person
where person.LAST_NAME = 'Johnson'
This won't work. You can only replace the 'Johnson' part dynamically not the whole condition. Thus what really gets generated is
SELECT person.ID, person.SSN, person.LAST_NAME, person.MIDDLE_NAME, person.FIRST_NAME
FROM PERSONS as person
where 'person.LAST_NAME = \'Johnson\''
Which obviously isn't a valid condition for the WHERE-part as there is only a literal but no column and operator to compare the field to.
If you only have to match against person.LAST_NAME rewrite the xml-sql-query to
<sql-query name="PersonQuery" resultset-ref="PersonSet" read-only="true" >
<![CDATA[
SELECT
...
FROM PERSONS as person
where person.LAST_NAME = :value
]]>
</sql-query>
And in the C# code set
String query = "Johnson";
If you need to dynamically filter by different columns or even multiple columns at a time use filters. e.g. like this (i made a few assumptions on you hibernate-mapping file)
<hibernate-mapping>
...
<class name="Person">
<id name="id" type="int">
<generator class="increment"/>
</id>
...
<filter name="ssnFilter" condition="ssn = :ssnValue"/>
<filter name="lastNameFilter" condition="lastName = :lastNameValue"/>
<filter name="firstNameFilter" condition="firstName = :firstNameValue"/>
<filter name="middleNameFilter" condition="middleName = :middleNameValue"/>
</class>
...
<sql-query name="PersonQuery" resultset-ref="PersonSet" read-only="true" >
...
FROM PERSONS as person
]]>
</sql-query>
<!-- note the missing WHERE clause in the PersonQuery -->
...
<filter-def name="ssnFilter">
<filter-param name="ssnValue" type="int"/>
</filter-def>
<filter-def name="lastNameFilter">
<filter-param name="lastNameValue" type="string"/>
</filter-def>
<filter-def name="middleNameFilter">
<filter-param name="midlleNameValue" type="string"/>
</filter-def>
<filter-def name="firstNameFilter">
<filter-param name="firstNameValue" type="string"/>
</filter-def>
</hibernate-mapping>
Now in your code you should be able to do
String lastName = "Johnson";
String firstName = "Joe";
//give me all persons first
HibernateTemplate.FindByNamedQuery("PersonQuery");
//just give me persons WHERE FIRST_NAME = "Joe" AND LAST_NAME = "Johnson"
Filter filter = HibernateTemplate.enableFilter("firstNameFilter");
filter.setParameter("firstNameValue", firstName);
filter = HibernateTemplate.enableFilter("lastNameFilter");
filter.setParameter("lastNameValue", lastName);
HibernateTemplate.FindByNamedQuery("PersonQuery");
//oh wait. Now I just want all Johnsons
HibernateTemplate.disableFilter("firstNameFilter");
HibernateTemplate.FindByNamedQuery("PersonQuery");
//now again give me all persons
HibernateTemplate.disableFilter("lastNameFilter");
HibernateTemplate.FindByNamedQuery("PersonQuery");
If you need still more dynamic queries (e.g. even changing the operator (=, !=, like, >, <, ...) or you have to combine restrictions logically (where lastname = "foo" OR firstname" = "foobar") then it's definitly time to look into the
Hibernate Criteria API
I'm unfamiliar with this HibernateTemplate syntax, but it looks like you're querying the original fieldname in SQL rather than the alias. Try this:
String query = "person.LastName = 'Johnson'";
or, maybe:
String query = "[person.LastName] = 'Johnson'";
or, possibly:
String query = "{person.LastName} = 'Johnson'";
Depends what sort of pre-processing is going on before the final SQL query is sent to the server.
That's because :value is a bind variable in the query; you cannot simply replace it with a string that contains an arbitrary string (that would become part of the query), only with an actual value. In your case, the value is "person.LAST_NAME = 'Johnson'", which is actually a string, not a boolean value. Boolean values would be true or false, both of which are rather useless for what you try to archive.
Bind variables more-or-less replace literals, not complex expressions.