Java Binding with Intersystems Cache - intersystems-cache

I'm new to Cache and I'm trying to use java binding and I have some problems with it.
1) I want to add some entry to the database using code
Database dbconnection = CacheDatabase.getDatabase (url, username, password);
Patient patient = new Patient(dbconnection);
patient.setFIO("Antonov Kirill Vladimirovich");
Diary diary = new Diary(dbconnection);
diary.setData("Very bad.");
diary.setDate(new java.sql.Date(2015,11,12));
diary.setStatus("Unsatisfied");
ListOfObjects obj = new ListOfObjects(dbconnection);
obj.add(diary);
patient.listOfDiariesSetObject(new Oid(obj));
dbconnection.saveAllObjects();
This code causes
Exception in thread "main" java.lang.ClassCastException: com.intersys.classes.ListOfObjects cannot be cast to com.intersys.jdbc.SysList
at com.intersys.jdbc.SysListProxy.getBinaryData(SysListProxy.java:516)
at com.intersys.objects.Oid.getData(Oid.java:101)
at com.intersys.cache.Dataholder.<init>(Dataholder.java:378)
at smda.Patient.listOfDiariesSetObject(Patient.java:1565)
at etu.wollen.cache.DBConnector.main(DBConnector.java:34)
How should I convert ListOfObjects to Oid correctly?
2) How should I remove some entries from database? I have found only save methods in com.intersys.objects.Database using \Dev\java\samples\doc
3) Most of classes, such as com.intersys.objects.Database, com.intersys.objects.CacheException, ... are deprecated. But official docbook still uses theese classes. Should I use deprecated classes?
Patient
Class smda.Patient Extends %Persistent
{
Property FIO As %String;
Property RegNumber As %String;
Property MedCardNumber As %String;
Property listOfDiaries As list Of Diary;
Property listOfEpisodes As list Of Episode;
Storage Default
{
<Data name="PatientDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>FIO</Value>
</Value>
<Value name="3">
<Value>RegNumber</Value>
</Value>
<Value name="4">
<Value>MedCardNumber</Value>
</Value>
<Value name="5">
<Value>listOfDiaries</Value>
</Value>
<Value name="6">
<Value>listOfEpisodes</Value>
</Value>
</Data>
<DataLocation>^smda.PatientD</DataLocation>
<DefaultData>PatientDefaultData</DefaultData>
<IdLocation>^smda.PatientD</IdLocation>
<IndexLocation>^smda.PatientI</IndexLocation>
<StreamLocation>^smda.PatientS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}
Projection PatientJava As %Projection.Java(ROOTDIR = "C:\Projects\Cache\Java");
}
Diary
Class smda.Diary Extends %Persistent
{
Property Data As %Text(MAXLEN = 1000);
Property Status As %String;
Property Date As %Date;
Storage Default
{
<Data name="DiaryDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Data</Value>
</Value>
<Value name="3">
<Value>Status</Value>
</Value>
<Value name="4">
<Value>Date</Value>
</Value>
</Data>
<DataLocation>^smda.DiaryD</DataLocation>
<DefaultData>DiaryDefaultData</DefaultData>
<IdLocation>^smda.DiaryD</IdLocation>
<IndexLocation>^smda.DiaryI</IndexLocation>
<StreamLocation>^smda.DiaryS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}
}

I think the answer to your first question is in your code. I think you can modify this section:
ListOfObjects obj = new ListOfObjects(dbconnection);
obj.add(diary);
patient.listOfDiariesSetObject(new Oid(obj));
to be as follows:
patient.listOfDiariesSetObject(new Oid(diary));
Your initial code is creating a ListOfObjects instance that you are then inserting into the listOfDiaries property, rather than inserting your Diary instance into the list.

1) Just get the value, for this property, it should contain a value with type java.util.List. And then you can manipulate with this list, such as add your value.
instead of
ListOfObjects obj = new ListOfObjects(dbconnection);
obj.add(diary);
patient.listOfDiariesSetObject(new Oid(obj));
should be
List diaries = patient.getlistOfDiaries();
diaries.add(diary);
2) Every Caché class has own method for create (%New), open (%OpenId) and for delete (%DeleteId) by id objects of this classes. And, projected classes, has their own such static methods. Here in documentation you can see some details about projection classes. So, you can call such code, for delete object with Id=1:
Patient.sys_DeleteId(dbconnection, 1);
3) Not sure, but I think it is because of Caché eXTreme, which should replace cachedb.jar. You can read more about Java Caché eXTreme here.

Related

MyBatis - Returning a HashMap

I want the returned result of the select statement below to be Map<String, Profile>:
<select id="getLatestProfiles" parameterType="string" resultMap="descProfileMap">
select ml.layerdescription, p1.*
from ( select max(profile_id) as profile_id
from SyncProfiles
group by map_layer_id) p2
inner join SyncProfiles p1 on p1.profile_id = p2.profile_id
inner join maplayers ml on ml.LAYERID = p1.MAP_LAYER_ID
where ml.maxsite = #{site}
</select>
I have seen this post which maps a String to a custom class, but the key was part of the custom class. In my query above, the layerdescription field is not part of the Profile class since I'm aiming to have the Profile class strictly represent the syncprofiles table and the layerdescription field is in another table.
My interface looks like:
public Map<String, Profile> getLatestProfiles(final String site);
How should descProfileMap be defined? I want to do something like:
<resultMap id="descProfileMap" type="java.util.HashMap">
<id property="key" column="layerdescription" />
<result property="value" javaType="Profile"/>
</resultMap>
But this is clearly wrong. Thanks for your help!
Achieving this requires 2 steps:
-Use association and nested resultMap:
<resultMap type="Profile" id="profileResultMap">
<!-- columns to properties mapping -->
</resultMap
<resultMap type="map" id="descProfileMap">
<id property="key" column="layerdescription" />
<association property="value" resultMap="profileResultMap" />
</resultMap>
-Add every record to a Map with expected structure using ResultHandler:
final Map<String, Profile> finalMap = new HashMap<String, Profile>();
ResultHandler handler = new ResultHandler() {
#Override
public void handleResult(ResultContext resultContext) {
Map<String, Object> map = (Map) resultContext.getResultObject();
finalMap.put(map.get("key").toString()), (Profile)map.get("value"));
}
};
session.select("getLatestProfiles", handler);
If you run that as is, expect this exception will likely be raised:
org.apache.ibatis.executor.ExecutorException: Mapped Statements with
nested result mappings cannot be safely used with a custom
ResultHandler. Use safeResultHandlerEnabled=false setting to bypass
this check or ensure your statement returns ordered data and set
resultOrdered=true on it.
Then following the suggestion, you can either disable the check globally in Mybatis config:
According to the documentation:
safeResultHandlerEnabled: Allows using ResultHandler on nested statements. If allow, set the
false. Default: true.
<settings>
<setting name="safeResultHandlerEnabled" value="false"/>
</settings>
or specify your result is ordered in the statement:
The documentation states:
resultOrdered This is only applicable for nested result select
statements: If this is true, it is assumed that nested results are
contained or grouped together such that when a new main result row is
returned, no references to a previous result row will occur anymore.
This allows nested results to be filled much more memory friendly.
Default: false.
<select id="getLatestProfiles" parameterType="string" resultMap="descProfileMap" resultOrdered="true">
But I have not found anyway to specify this statement option when using annotations.

SqlPagingQueryProviderFactoryBean to support In clause

I want to write an SQL for SqlPagingQueryProviderFactoryBean. I will pass the parameter for an IN clause. I am not getting the result when I am passing it as a parameter(?). But I am getting the correct result when I am hard coding the values.
Please guide me on this.
You cannot have a single place holder and replace it with an array due to sql injection security policy, However the gettter/setters of sqlPagingQueryProvider properties selectclause, fromClause and whereCLause are String and not preparedStatement. The PreparedStatement would be constructed later by spring batch during post construct method. Hence you can send the where clause as String with values(Prepared) and pass it to job as parameter. Hence your code would something of this sort.
String topics = { "topic1", "topic2", "topic3", "topic4"};
StringBuilder str = new StringBuilder("('");
for (String topic : topics) {
str.append(topic + "','");
}
str.setLength(str.length() - 2);
str.append("')");
final JobParameters jobParameters = new JobParametersBuilder()
.addLong("time", System.nanoTime())
.addString("inputsTopics", str.toString())
.toJobParameters();
And your pagingreader bean would look like below and make sure you set scope to step
<bean id="sqlPagingReader" class="<your extended prging prvder>.KPPageingProvider" scope="step" >
<property name="dataSource" ref="dataSource" />
<property name="selectClause" value="u.topic,cu.first_name ,cu.last_name, cu.email" />
<property name="fromClause" value="ACTIVE_USER_VIEWS_BY_TOPIC u inner join cl_user cu on u.user_id=cu.id" />
<property name="whereClause" value="u.topic in #{jobParameters['inputsTopics']}" ></property>
</bean>

Indexed getter only in Dozer

I'm struggling with a mapping in Dozer. The basic structure of my classes is this:
class Foo {
private String someString;
public String getSomeString() {
return someString;
}
public void setSomeString(String someString) {
this.someString = someString;
}
}
and the interesting part:
class Bar {
// note that no field is declared
public String[0] getSomeBarString() {
// This returns an array where the acctually desired string is a index 0
}
public void setSomeBarString(String someString) {
// stores the string otherwise
}
}
Compensating the absence of a field and the differently named getter/setter methods was quite easy:
<mapping>
<class-a>Foo</class-a>
<class-b>Bar</class-b>
<field>
<a>someString</a>
<b get-method="getSomeBarString" set-method="setSomeBarString">someBarString</b>
</field>
</mapping>
From my understanding I could even omitt get-method and set-method as there is no field access by default.
My problem is that the getter is indexed and the setter isn't. I've already read about indexed property mapping but it does it both ways. Is there a way to make only one direction indexed? E.g. would get-method="getSomeBarString[0]" work?
After a night of sleep I got an idea myself. I just define two one way mappings and make one of them indexed. It also turns out indexing is defined the same way (after the property name) even if you declare a different get-method or set-method.
<mapping type="one-way">
<class-a>Foo</class-a>
<class-b>Bar</class-b>
<field>
<a>someString</a>
<b set-method="setSomeBarString">someBarString</b>
</field>
</mapping>
<mapping type="one-way">
<class-a>Bar</class-a>
<class-b>Foo</class-b>
<field>
<a get-method="getSomeBarString">someBarString[0]</a>
<b>someString</b>
</field>
</mapping>

Getting Data from Multiple tables in Liferay 6.0.6

i'm trying to get data from multiple tables in liferay 6.0.6 using custom sql, but for now i'm just able to display data from one table.does any one know how to do that.thanks
UPDATE:
i did found this link http://www.liferaysavvy.com/2013/02/getting-data-from-multiple-tables-in.html but for me it's not working because it gives an error BeanLocator is null,and it seems that it's a bug in liferay 6.0.6
The following technique also works with liferay 6.2-ga1.
We will consider we are in the portlet project fooproject.
Let's say you have two tables: article, and author. Here are the entities in your service.xml :
<entity name="Article" local-service="true">
<column name="id_article" type="long" primary="true" />
<column name="id_author" type="long" />
<column name="title" type="String" />
<column name="content" type="String" />
<column name="writing_date" type="Date" />
</entity>
<entity name="Author" local-service="true">
<column name="id_author" type="long" primary="true" />
<column name="full_name" type="String" />
</entity>
At that point run the service builder to generate the persistence and service layers.
You have to use custom SQL queries as described by Liferay's Documentation to fetch info from multiple databases.
Here is the code of your fooproject-portlet/src/main/ressources/default.xml :
<?xml version="1.0"?>
<custom-sql>
<sql file="custom-sql/full_article.xml" />
</custom-sql>
And the custom request in the fooproject-portlet/src/main/ressources/full_article.xml:
<?xml version="1.0"?>
<custom-sql>
<sql
id="com.myCompany.fooproject.service.persistence.ArticleFinder.findByAuthor">
<![CDATA[
SELECT
Author.full_name AS author_name
Article.title AS article_title,
Article.content AS article_content
Article.writing_date AS writing_date
FROM
fooproject_Article AS Article
INNER JOIN
fooproject_Author AS Author
ON Article.id_author=Author.id_author
WHERE
author_name LIKE ?
]]>
</sql>
</custom-sql>
As you can see, we want to fetch author's name, article's title, article's content and article's date.
So let's allow the service builder to generate a bean that can store all these informations. How ? By adding it to the service.xml ! Be careful: the fields of the bean and the fields' name returned by the query must match.
<entity name="ArticleBean">
<column name="author_name" type="String" primary="true" />
<column name="article_title" type="String" primary="true" />
<column name="article_content" type="String" />
<column name="article_date" type="Date" />
</entity>
Note: defining which field is primary here does not really matter as there will never be anything in the ArticleBean table. It is all about not having exceptions thrown by the service builder while generating the Bean.
The finder method must be implemented then. To do so, create the class com.myCompany.fooproject.service.persistence.impl.ArticleFinderImpl. Populate it with the following content:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> {
}
Use the correct import statements and run the service builder. Let's make that class implement the interface generated by the service builder:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> implements ArticleFinder {
}
And populate it with the actual finder implementation:
public class ArticleFinderImpl extends BasePersistenceImpl<Article> implements ArticleFinder {
// Query id according to liferay's query naming convention
public static final String FIND_BY_AUTHOR = ArticleFinder.class.getName() + ".findByAuthor";
public List<Article> findByAuthor(String author) {
Session session = null;
try {
session = openSession();
// Retrieve query
String sql = CustomSQLUtil.get(FIND_BY_AUTHOR);
SQLQuery q = session.createSQLQuery(sql);
q.setCacheable(false);
// Set the expected output type
q.addEntity("StaffBean", StaffBeanImpl.class);
// Binding arguments to query
QueryPos qpos = QueryPos.getInstance(q);
qpos.add(author);
// Fetching all elements and returning them as a list
return (List<StaffBean>) QueryUtil.list(q, getDialect(), QueryUtil.ALL_POS, QueryUtil.ALL_POS);
} catch(Exception e) {
e.printStackTrace();
} finally {
closeSession(session);
}
return null;
}
}
You can then call this method from your ArticleServiceImpl, whether it is to make a local or a remote API.
Note: it is hack. This is not a perfectly clean way to retrieve data, but it is the "less bad" you can do if you want to use Liferay's Service Builder.

liferay-6.1 - Implement own service

Hey I have create my own service.xml with student. Now o want to add my own searchByName method for student. can you please explain me what to write in StudentLocalServiceImpl.
public class StudentLocalServiceImpl extends StudentLocalServiceBaseImpl {
/*
* NOTE FOR DEVELOPERS:
*
*/
public List<Student> getAll() throws SystemException {
return studentPersistence.findAll();
}
public Student getStudentByName(String name) {
return studentPersistence.
}
// I have created one method getAll. I need help for the another one.
Thanks in Advance.
You would first declare this as a "finder" element in the service.xml within the entity you defined.
e.g.
<finder name="Name" return-type="Student">
<finder-column name="name" />
</finder>
The return-type could also be Collection if wanting a List<Student> as the return type, if name is not unique.
<finder name="Name" return-type="Collection">
<finder-column name="name" />
</finder>
You can also state a comparison operator for the column:
<finder name="NotName" return-type="Collection">
<finder-column name="name" comparator="!=" />
</finder>
A finder can actually declare a unique index as well to be generated on this relation (will be applied to the DB table) by specifying the unique="true" attribute on the finder:
<finder name="Name" return-type="Student" unique="true">
<finder-column name="name" />
</finder>
With this definition and after re-runing ant build-service the studentPersistence will contain new methods using the name of the finder found in the xml element appended with a prefix: countBy, findBy, fetchBy, removeBy, etc.
Finally, your serice method would only need to contain the following (based on the above):
public Student getStudentByName(String name) throws SystemException {
return studentPersistence.findByName(name);
}
HTH