Ebean calling stored procedure and converting ResultSet to Model - mysql

I'm working in report module, in order to do that I'm creating different stored procedures. I create the procedure with in parameters and then create a class to map the row (resultSet)
I think that's the best way to work arround performance and clarity.(what do you think about that?)
I'm using play framework and ebean orm (2.7.7)
I'm calling the store procedure and getting the resultSet, but I would like to use ebean in order to cast automaticly the row to model... other option is take the row-cell and cast it in a property but I'm trying to avoid it.
This is the current approach
Is this the best way to call an stored procedure?
Transaction tx = Ebean.beginTransaction();
String sql = "{CALL report(?, ?, ?, ?, ?, ?)}";
CallableStatement callableStatement = null;
try {
Connection dbConnection = tx.getConnection();
callableStatement = dbConnection.prepareCall(sql);
callableStatement.setInt(1, 3);
callableStatement.setInt(2, 5);
callableStatement.setInt(3, 2013);
callableStatement.setInt(4, 1);
callableStatement.setInt(5, 2014);
callableStatement.setInt(6, 5);
ResultSet rs = callableStatement.executeQuery(sql);
while (rs.next()) {
//HOW TO CONVER row -> model ?
}
Ebean.commitTransaction();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
I've discarded RawSQL and Query because received an error
RuntimeException: Error parsing sql, can not find SELECT keyword in: xxxxx
Also I found other option... using CallableSql
String sql = "{call sp_order_mod(?,?)}";
CallableSql cs = Ebean.createCallableSql(sql);
cs.setParameter(1, "turbo");
cs.registerOut(2, Types.INTEGER);
Ebean.execute(cs);
// read the out parameter
Integer returnValue = (Integer) cs.getObject(2);
but in this case I need to return a ResultSet not simply parameter.

I'm going to share my own solution.
I get a class called ResultSetUtils.(you can google it some implementation)
I added a generic method in order to return a typed list from resultset
public static <T> List<T> populateInList(Class<T> c, final ResultSet rs) {
List<T> listTyped = new ArrayList<T>();
try {
if (rs != null) {
while (rs.next()) {
T o = c.newInstance();
// MAGIC LINE
populate(o, rs);
listTyped.add(o);
}
rs.close();
}
} catch (final Exception e) {
// TODO Auto-generated catch block
System.err.println(e.getMessage());
}
return listTyped;
}
This class to do the population use org.apache.commons.beanutils package
BeanUtils.populate(bean, propertiesRealName);
Using
private static void callingProcedureTest() {
Logger.debug("Init callingProcedureTest");
Transaction tx = Ebean.beginTransaction();
// String sql = "{CALL sp_report_test(3, 5, 2013, 1, 2014, 5)}";
String sql = "CALL sp_report_test(?, ?, ?, ?, ?, ?);";
try {
Connection dbConnection = tx.getConnection();
CallableStatement callableStatement = dbConnection.prepareCall(sql);
callableStatement.setInt(1, 3);
callableStatement.setInt(2, 5);
callableStatement.setInt(3, 2013);
callableStatement.setInt(4, 1);
callableStatement.setInt(5, 2014);
callableStatement.setInt(6, 5);
Logger.debug("SQL > " + sql);
ResultSet rs = callableStatement.executeQuery();
Class<ReportTestResult> c = ReportTestResult.class;
//************** MAGIC LINE, converting ResultSet to Model
List<ReportTestResult> listResult = ResultSetUtils.populateInList(c, rs);
for (ReportTestResult item : listResult) {
Logger.debug("item.firstName> " + item.firstName);
Logger.debug("item.lastName > " + item.lastName);
Logger.debug("item.year > " + item.year);
}
Ebean.commitTransaction();
} catch (Exception e) {
Ebean.rollbackTransaction();
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
Ebean.endTransaction();
}
}
Plus about architecture and implementation
For each report I'm going to create:
a Result class (eg ReportTestResult)
intention: represent a row of report | simple DTO
a Param class (eg ReportTestParam),
intention: represent the parameters (inputs / ouputs), filters of the report
This class should implements
public interface ReportParam {
public int countParameteres();
public void setParametersInCallableStatement(CallableStatement callableStatement) throws SQLException;
}
a Report class (eg ReportTestReport) this class should extends ReportBase
intention: Knows the stored procedure's name, parameters and dto result
public class ReportTestReport extends ReportBase<ReportTestResult, ReportTestParam> {
#Override
protected String getProcedureName() {
return STORED_NAME;
}
}
many Adapters...
Each report could displayed in different charts, In this case I'm using HighCharts. Order to arrange it, I'm creating different adapters to do that.
EG:
class ReportTestHighChartsAdapter
intention: convert a list of ReportTestResult to series and configure different options of report (eg, title, xAxis etc)
public OptionsHC buildColumnReportV1(){
OptionsHC optionChart = new OptionsHC();
optionChart.chart = new ChartHC("column");
this.setTitle(optionChart);
optionChart.yAxis = new AxisHC(new TitleHC("Fruit eaten"));
.....
return optionChart;
}
OptionsHC is a class that represent option obj in the HighCharts framework.
The final step is converting OptionHC to Json and use it in JavaScript (common use of highCharts)
What's ReportBase?
ReportBase class has the strategy to implements the final called to DB, also manage the transaction
public class ReportTestReport extends ReportBase<ReportTestResult, ReportTestParam> {
...
protected List<TResult> execute(Class<TResult> classT) {
List<TResult> resultDTO = null;
CallableStatement callableStatement = null;
Logger.debug("Running procedure> " + this.getProcedureName());
Transaction tx = Ebean.beginTransaction();
String sql = ProcedureBuilder.build(this.getProcedureName(), this.countParameters());
Logger.debug("SQL > " + sql);
try {
Connection dbConnection = tx.getConnection();
callableStatement = dbConnection.prepareCall(sql);
this.getFilter().setParametersInCallableStatement(callableStatement);
ResultSet rs = callableStatement.executeQuery();
resultDTO = ResultSetUtils.populateInList(classT, rs);
Ebean.commitTransaction();
Logger.debug("commitTransaction > " + sql);
} catch (Exception e) {
Ebean.rollbackTransaction();
Logger.debug("rollbackTransaction > " + sql);
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
Ebean.endTransaction();
}
return resultDTO;
}
...
}

Currently the support for stored procedures in Ebean is not orientated to what you are trying to do. Hence you are not going to get much joy from using CallableSql or RawSql.
>> a class to map the row (resultSet) I think that's the best way to work around performance and clarity
Yes, I can understand your motivation.
>> How to convert ResultSet into model
Currently there is no good solution. The best solution would be to enhance RawSql so that you can set a ResultSet onto it. One of the things RawSql does is provide the mapping of resultSet columns to model properties and that is what Ebean needs internally. The enhancement/code change would be to be able to set a resultSet onto the RawSql object ... and get Ebean internally to skip the creation of the resultSet ( preparedStatement, binding parameters and executeQuery()). In terms of Ebean internals this is all done in the CQuery.prepareBindExecuteQueryWithOption() method. That is, if the RawSql has already provided a resultSet skip those things.
The big benefit of doing this rather than just rolling your own row -> model mapping code is that the resulting beans would all still have lazy loading / partial object knowledge etc. They would behave exactly like any other beans that Ebean builds as part of it query mechanism.
So that said, I'm personally away for a week ... so you aren't going to hear back from me until after that. If you want to get into it yourself then internally CQuery.prepareBindExecuteQueryWithOption() is the code you will need to modify.
If you have been following the ebean google group you'll know that but just in case you have not been note that the Model and Finder objects from Play have been incorporated into Ebean just in the last week. This helps both projects ... reduces confusion etc. The Ebean source in github master is at 4.0.4 and the bytecode enhancement in 4.x is different and I don't believe supported in Play.
I'm basically going offline for a week now so I'll look back into this after that.
Cheers, Rob.

Related

Concurrent Read/Write MySQL EF Core

Using EF Core 2.2.6 and Pomelo.EntityFrameworkCore.MySql 2.2.6 (with MySqlConnector 0.59.2)). I have a model for UserData:
public class UserData
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public ulong ID { get; private set; }
[Required]
public Dictionary<string, InventoryItem> Inventory { get; set; }
public UserData()
{
Data = new Dictionary<string, string>();
}
}
I have a REST method that can be called that will add items to the user inventory:
using (var transaction = context.Database.BeginTransaction())
{
UserData data = await context.UserData.FindAsync(userId);
// there is code here to detect duplicate entries/etc, but I've removed it for brevity
foreach (var item in items) data.Inventory.Add(item.ItemId, item);
context.UserData.Update(data);
await context.SaveChangesAsync();
transaction.Commit();
}
If two or more calls to this method are made with the same user id then I get concurrent accesses (despite the transaction). This causes the data to sometimes be incorrect. For example, if the inventory is empty and then two calls are made to add items simultaneously (item A and item B), sometimes the database will only contain either A or B, and not both. From logging it appears that it is possible for EF to read from the database while the other read/write is still occurring, causing the code to have the incorrect state of the inventory for when it tries to write back to the db. So I tried marking the isolation level as serializable.
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
Now I sometimes see an exception:
MySql.Data.MySqlClient.MySqlException (0x80004005): Deadlock found when trying to get lock; try restarting transaction
I don't understand how this code could deadlock... Anyways, I tried to proceed by wrapping this whole thing in a try/catch, and retry:
public static async Task<ResponseError> AddUserItem(Controller controller, MyContext context, ulong userId, List<InventoryItem> items, int retry = 5)
{
ResponseError result = null;
try
{
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
UserData data = await context.UserData.FindAsync(userId);
// there is code here to detect duplicate entries/etc, but I've removed it for brevity
foreach (var item in items) data.Inventory.Add(item.ItemId, item);
context.UserData.Update(data);
await context.SaveChangesAsync();
transaction.Commit();
}
}
catch (Exception e)
{
if (retry > 0)
{
await Task.Delay(SafeRandomGenerator(10, 500));
return await AddUserItem(controller, context, userId, items, retry--);
}
else
{
// store exception and return error
}
}
return result;
}
And now I am back to the data being sometimes correct, sometimes not. So I think the deadlock is another problem, but this is the only method accessing this data. So, I'm at a loss. Is there a simple way to read from the database (locking the row in the process) and then writing back (releasing the lock on write) using EF Core? I've looked at using concurrency tokens, but this seems overkill for what appears (on the surface to me) to be a trivial task.
I added logging for mysql connector as well as asp.net server and can see the following failure:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
=> RequestId:0HLUD39EILP3R:00000001 RequestPath:/client/AddUserItem => Server.Controllers.ClientController.AddUserItem (ServerSoftware)
Failed executing DbCommand (78ms) [Parameters=[#p1='?' (DbType = UInt64), #p0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
UPDATE `UserData` SET `Inventory` = #p0
WHERE `ID` = #p1;
SELECT ROW_COUNT();
A total hack is to just delay the arrival of the queries by a bit. This works because the client is most likely to generate these calls on load. Normally back-to-back calls aren't expected, so spreading them out in time by delaying on arrival works. However, I'd rather find a correct approach, since this just makes it less likely to be an issue:
ResponseError result = null;
await Task.Delay(SafeRandomGenerator(100, 500));
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
// etc
This isn't a good answer, because it isn't what I wanted to do, but I'll post it here as it did solve my problem. My problem was that I was trying to read the database row, modify it in asp.net, and then write it back, all within a single transaction and while avoiding deadlocks. The backing field is JSON type, and MySQL provides some JSON functions to help modify that JSON directly in the database. This required me to write SQL statements directly instead of using EF, but it did work.
The first trick was to ensure I could create the row if it didn't exist, without requiring a transaction and lock.
INSERT INTO UserData VALUES ({0},'{{}}','{{}}') ON DUPLICATE KEY UPDATE ID = {0};
I used JSON_REMOVE to delete keys from the JSON field:
UPDATE UserData as S set S.Inventory = JSON_REMOVE(S.Inventory,{1}) WHERE S.ID = {0};
and JSON_SET to add/modify entries:
UPDATE UserData as S set S.Inventory = JSON_SET(S.Inventory,{1},CAST({2} as JSON)) WHERE S.ID = {0};
Note, if you're using EF Core and want to call this using FromSql then you need to return the entity as part of your SQL statement. So you'll need to add something like this to each SQL statement:
SELECT * from UserData where ID = {0} LIMIT 1;
Here is a full working example as an extension method:
public static async Task<UserData> FindOrCreateAsync(this IQueryable<UserData> table, ulong userId)
{
string sql = "INSERT INTO UserData VALUES ({0},'{{}}','{{}}') ON DUPLICATE KEY UPDATE ID = {0}; SELECT * FROM UserData WHERE ID={0} LIMIT 1;";
return await table.FromSql(sql, userId).SingleOrDefaultAsync();
}
public static async Task<UserData> JsonRemoveInventory(this DbSet<UserData> table, ulong userId, string key)
{
if (!key.StartsWith("$.")) key = $"$.\"{key}\"";
string sql = "UPDATE UserData as S set S.Inventory = JSON_REMOVE(S.Inventory,{1}) WHERE S.ID = {0}; SELECT * from UserData where ID = {0} LIMIT 1;";
return await table.AsNoTracking().FromSql(sql, userId, key).SingleOrDefaultAsync();
}
Usage:
var data = await context.UserData.FindOrCreateAsync(userId);
await context.UserData.JsonRemoveInventory(userId, itemId);

p:autocomplete tag using database

I want to use autocomplete in inputTextArea. I am doing it using values from the database. I have words, digits, symbols(like #) stored in the database.
The problem is when I try typing in the textArea, the whole list of things appears. Instead, I just want only those options to come which matches the input written in the textArea, kind of autocomplete feature but it fetches values from a database.
Given below is the java code that I have written so far.
public class DbConnect {
public List<String> completeArea(String query1) {
ResultSet rs;
Statement st;
Connection con;
PreparedStatement pst;
List<String> result = new ArrayList<String>();
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection("jdbc:mysql://localhost/company", "root", "");
try {
query1 = "select name from labels";
pst = con.prepareStatement(query1);
rs = pst.executeQuery();
while (rs.next()) {
result.add(rs.getString("name"));
}
} catch (Exception ex) {
System.out.println(ex);
}
} catch (Exception ex) {
System.out.println("error occured" + ex);
}
System.out.println("size is " + result.size());
return result;
}
I do not want to specify any particular letter for searching in the database, it should pick automatically when the user types in. Any help would do good. Thanks a lot.
In your example the query1 parameter of the completeArea method is the user input but you overwriting it with your query. Try with
public List<String> completeArea(String input) {
...
String query = "select name from labels where name like ?";
pst = con.prepareStatement(query);
pst.setString(1, input + "%");
...
}
Edited according to #Slaw comment. Thanks for the correction. :)
I would use <p:autoComplete /> instead of inputTextArea for this usecase.
You can find a good tutorial in the official PrimeFaces site.
Your query is
select name from labels
that will give all labels's name...
I do not want to specify any particular letter for searching in the database, it should pick automatically when the user types in. Any help would do good.
If you want some matching you must specify a condition

Load 3D model in Unity using Resource folder and Mysql

I want to load 3D model using Resource folder. I created an sql database to store the address. In this case I stored the file "deer-3ds" in folder "Models" and also save these information in a table named "modeladdress" in sql.
So please help me to correct my code. I know that it's 100% wrong but I dont know how to fix it. Thank you.
using UnityEngine;
using System.Collections;
using System;
using System.Data;
using Mono.Data.Sqlite;
public class addobject : MonoBehaviour {
// Use this for initialization
void Start () {
//GameObject deer=Instantiate(Resources.Load("deer-3d.bak",typeof(GameObject)))as GameObject;
// GameObject instance = Instantiate(Resources.Load("Models/deer-3ds", typeof(GameObject))) as GameObject;
string conn = "URI=file:" + Application.dataPath + "/modeladdress.s3db"; //Path to database.
IDbConnection dbconn;
dbconn = (IDbConnection) new SqliteConnection(conn);
dbconn.Open(); //Open connection to the database.
IDbCommand dbcmd = dbconn.CreateCommand();
string sqlQuery = "SELECT ordinary,foldername, filename " + "FROM modeladdress";
dbcmd.CommandText = sqlQuery;
IDataReader reader = dbcmd.ExecuteReader();
while (reader.Read ()) {
int ordinary = reader.GetInt32 (0);
string foldername = reader.GetString (1);
string filename = reader.GetString (2);
string path = foldername + "/" + filename;
//Debug.Log( "value= "+value+" name ="+name+" random ="+ rand);
GameObject instance = Instantiate(Resources.Load(path, typeof(GameObject))) as GameObject;
instance.SetActive (true);
}
reader.Close();
reader = null;
dbcmd.Dispose();
dbcmd = null;
dbconn.Close();
dbconn = null;
}
// Update is called once per frame
void Update () {
// GameObject instance = Instantiate(Resources.Load("Models/deer-3ds", typeof(GameObject))) as GameObject;
// instance.SetActive (true);
}
}
First of all, you are using SQLite at your database management system, not MySQL. Second, the way you have written your query,
string sqlQuery = "SELECT ordinary,foldername, filename " + "FROM modeladdress";
Will return the ordinary, foldername, and filename for every model. You need to use a WHERE clause to specify precisely which model you want to use. Thus, you need some way to know which model you want to query from the database before you actually execute the query, and in that case, why even query a database? You're going to have to store some unique identifier anyway so a database solves nothing.
Now concerning the actual code you have written, it appears to be correct (i.e. it should be returning what you want). The problem must be that either your table is empty, your values that are returned are incorrect, or that the object is being instantiated in an incorrect location and thus you are thinking it's not working. If you want a more concrete answer you'll have to comment on this answer with the specific problem you are facing (i.e. what specifically is "wrong"?).

Error while Fetching Records using Custom Query in Liferay

I am passing Array of long values and trying to get the relevant records But it is throwing a Error Positional Parameter is not defined.
Here is the code
try {
String list = CustomSQLUtil.get(id of the query);
SQLQuery sqlQuery = session.createSQLQuery(list);
sqlQuery.setCacheable(false);
QueryPos q=QueryPos.getInstance(sqlQuery);
q.add(array of long values);
return processObjectListToEntityList(QueryUtil.list(sqlQuery, getDialect() ,QueryUtil.ALL_POS, QueryUtil.ALL_POS));
}
catch(Exception e){
log.error("Error while Fetching Records " + e);
} finally{
closeSession(session);
}
Query
SELECT
entity1.name
FROM
entityOne as entity1
JOIN
entityTwo as entity2
WHERE
entity1.id = entity2.id
AND
entity2.id IN (?);
Any help would be appreciated.
I recommend to parse your query to DinamycQuery API instead. This have many benefits as that you dont have to manage the session lifecycle and that you obtaing a List of liferay model objects directly.
An example would be:
DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(AssetCategory.class, PortalClassLoaderUtil.getClassLoader());
Property nameProperty = PropertyFactoryUtil.forName("name");
Property groupIdProperty = PropertyFactoryUtil.forName("groupId");
dynamicQuery.add(nameProperty.eq(name));
dynamicQuery.add(groupIdProperty.eq(groupId));
List<AssetCategory> categoriesByName = AssetCategoryLocalServiceUtil.dynamicQuery(dynamicQuery);

SQL: 4 classes must reference one central class which provides database access

I want to make several entries into a MySQL database. Because Some of the tables reference others via foreign key I have to get back the inserted ID to inject them in my next statements.
I have 4 classes:
LodgerFormTest
RentForm
RentObject
House
and the class which inserts the MySQL statements into the db: sql_statements
When I want to send a SQL statement I am getting a nullPointer Exception!
The Action listener of the House-class (this is the first sql-statement I have to send) looks like this:
saveButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("saveButton");
// sql_statements statements = new sql_statements();
sql_statements.performHouse(strasse.getText(), plz.getText(), ort.getText());
mainmenu.create();
rentnerFrame.dispose();
}
});
all methods and variables I am using in sql_statements are static! Therefore I am not instantiating an object.
here is the method "performHouse" in sql_statements
public static void performHouse(String strasse, String plz, String ort) {
String sql = "insert into haus(strasse, plz, ort) values (?,?,?)";
System.out.println(sql);
try{
ps = connect.prepareStatement(sql, ps.RETURN_GENERATED_KEYS);
ps.setString(1, strasse);
ps.setString(2, plz);
ps.setString(3, ort);
ps.execute();
rs = ps.getGeneratedKeys();
if(rs != null && rs.next()) {
// Retrieve the auto generated key(s).
key_idhaus = rs.getLong(1);
System.out.println("idhaus: " + key_idhaus);
}
}catch(Exception ex){
System.out.println(ex);
}
} // close performHouse-methode
I cant debug because I get a "Source not found." error in the debug view.
Can anybody help please?
okay I just wanted to finish the task so I implemented a solution which surely is all but not OO:
I implemented redundant code into all of the 4 classes and every class implements their own sql statements. quick and dirty ;)