Groovy SQL + Gorm under same session/transaction - sql

Please go through the simple scenario below, I couldn't find a better way to ask the question in textual form:
Two domain objects and a transactional service:
A {
int id
String prop1
B b
static constraints = {b nullable:true}
}
B {
int id
String prop1
// not worring about belongsTo here
}
SomeService {
def transactional = true
def sessionFactory
def aTransactionalMethod() {
Sql sql = new Sql(sessionFactory.currentSession.connection())
sql# create A some how with sql query leaving property b as null.
A a = A.findById(...)
//a.b must be null here, never mind
sql# create B object somehow with sql query.
// should a.b be available now? I'm getting null here.. session.currentSession.refresh(a) resolves the issue but why is that?
}
}

Hibernate can't parse SQL, so it doesn't "know" what you write to database. Hibernate will not reload all session objects - that would definitely be huge overhead.
Maybe if you rewrite your query to HQL the objects will become available to session in immediately.

Related

Compare two database fields in extbase repository

I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.

Using inline sql with entity frame work and c#

We are having som performance issue with EF and I want to try rewrite a query to inline sql. However I am having some difficulties, it is probably just a noob issue.
Lets say I have 3 classes: License, GroupLicense and Product
public class License
{
//stuff
Product MyProduct{get;set}
}
public class GroupLicense:License
{
// more stuff
}
public class Product
{
//product info stuff
}
Now I need to fetch some Grouplicenses depending on some requirements. However doing it with the datacontext and linq takes 2 minutes.
Something similar to this
var institutionLicenses = db.GroupLicenses
.Include(lic => lic.Product).Where(x => productIds.Contains(x.Product.Id) && x.LicenseStatus == StatusEnum.Active).ToList();
I want to do the same query using inline sql similar to this: I join the tables so all the primitive fields are okay.
var gl = db.Database.SqlQuery<GroupLicense>("select * from GroupLicense as g left join Licenses on g.Id =Licenses.Id").ToList();
(It is just example code - I know it is not working :))
However when executing, The base product property on License is null, all the primitive fields are there.
What do I need to change in my sql statement to make it work?

Grails sql queries

Imagine I have something like this:
def example = {
def temp = ConferenceUser.findAllByUser(User.get(session.user))
[temp: temp]
}
Explaining my problem:
Although dynamic finders are very easy to use and fast to learn, I must replace dynamic finders of my website for sql queries because it is a requirement. As I don't understand SQL that much, my main questions are:
a) I am using an SQLS database, with the drivers and datasource good configured and my website works as it is right now. If I want to replace the "findAllByUser" for an sql statement, should i do something like this:
def dataSource
...
def db = new Sql(dataSource)
def temp = db.rows("SELECT ... ")
b) And that will work? I mean, the temp object will be a list as it is if I use "findAllByUser", and do I need to open a connection to the database =?
With Grails you can use Dynamic Finders, Criteria Builders, Hibernate Query Language (HQL), or Groovy SQL.
To use Groovy SQL:
import groovy.sql.Sql
Request a reference to the datasource with def dataSource or def sessionFactory for transactions
Create an Sql object using def sql = new Sql(dataSource) or def sql = new Sql(sessionFactory.currentSession.connection())
Use Groovy SQL as required
Grails will manage the connection to the datasource automatically.
Sql.rows returns a list that can be passed to your view.
For example:
import groovy.sql.Sql
class MyController {
def dataSource
def example = {
def sql = new Sql(dataSource)
[ temp: sql.rows("SELECT . . .") ]
}
}
And within a transaction:
import groovy.sql.Sql
class MyController {
def sessionFactory
def example = {
def sql = new Sql(sessionFactory.currentSession.connection())
[ temp: sql.rows("SELECT . . .") ]
}
}
I recommend the book Grails Persistence with GORM and GSQL for a lot of great tips and techniques.
yes, with grails you can do both plain sql and hql queries. HQL is 'hibernate query language' and allows you to write sql-like statements, but use your domain classes and properties instead of the table names and column names. To do an hql query, do something like
def UserList = ConferenceUser.executeQuery('from ConferenceUser cu where cu.user = ?', [user]),
what you have here is a parameterized query -- executeQuery sees the ? in the hql string and substitutes the arguments in the array that is the second parameter to the method([user] in this case) for you.
See
http://grails.org/doc/latest/ref/Domain%20Classes/executeQuery.html
and you can see this on how to do sql queries with Grails
Sql query for insert in grails
Going Further / Tips
Use Spring beans
You can make the groovy.sql.Sql instance a Spring bean in your Grails application. In grails-app/conf/spring/resources.groovy define the Sql bean:
// File: grails-app/conf/spring/resources.groovy
beans = {
// Create Spring bean for Groovy SQL.
// groovySql is the name of the bean and can be used
// for injection.
sql(groovy.sql.Sql, ref('dataSource'))
}
Next inject the Sql instance in your your class.
package com.example
import groovy.sql.GroovyRowResult
class CarService {
// Reference to sql defined in resources.groovy.
def sql
List<GroovyRowResult> allCars(final String searchQuery) {
final String searchString = "%${searchQuery.toUpperCase()}%"
final String query = '''\
select id, make, model
from car
where ...
'''
// Use groovySql bean to execute the query.
final results = sql.rows(query, search: searchString)
results
}
}
Multiple Datasources
adminSql(groovy.sql.Sql, ref("dataSource_admin"))
userSql(groovy.sql.Sql, ref("dataSource_user"))
and inject the beans
def userSql
def adminSql
Into the services that need them.
or without injection
import groovy.sql.Sql
// ...
// inject the datasource bean
def dataSource_admin
// ...
// in a method
Sql sql = new Sql(dataSource_admin)
Early Grails Version
Looping through GORM result sets in early grails versions can cause needless queries in the middle of template loops. Using groovy SQL can help with this.

NHibernate / Localization / Lookup tables

I want to add localization support to my domain object. I have the following:
class Person
{
int Id;
City city;
}
class City
{
int Id;
string Name;
}
All cities are saved in a lookup db table Cities. I would like to have:
Person p = PeopleService.GetPersonById(1);
//Assert p.City.Name == 'London' if culture == 'en-us'
I dont like doing
string City::Name { get { return ILocalizationProvider.Get(typeof(City), Id); }
I came by this article:
http://ayende.com/Blog/archive/2006/12/26/LocalizingNHibernateContextualParameters.aspx
Yet I dont know whether its supported in NH 2.1 or not.
How can I instruct NH to cache all cities in 2nd-level cache to avoid joins each time for the same locale?
Is there an easy and neat way to treat database lookup tables and localization in NHibernate ?
In the article it says "Please note that this is no longer supported behavior in NHibernate 2.1 and up. It was a hack to begin with, and it isn't guaranteed to continue working."
So this will not work in your scenario. I would recommend this: http://nhforge.org/wikis/howtonh/localization-techniques.aspx

How do I Insert or Update (or overwrite) a record using NHibernate?

I need to write a row to the database regardless of whether it already exists or not. Before using NHibernate this was done with a stored procedure. The procedure would attempt an update and if no rows were modified it would fallback to an insert. This worked well because the application doesn't care if the record exists.
With NHibernate, the solutions I have found require loading the entity and modifying it, or deleting the entity so the new one can be inserted. The application does have to care if the record already exists. Is there a way around that?
Does the Id Matter?
Assigned Id
The object has a keyword as an assigned id and is the primary key in the table.
I understand that SaveOrUpdate() will call the Save() or Update() method as appropriate based on the Id. Using an assigned id, this won't work because the id isn't an unsaved-value. However a Version or Timestamp field could be used as an indicator instead. In reality, this isn't relevant because this only reflects on whether the object in memory has been associated with a record in the database; it does not indicate if the record exists or not in the database.
Generated Id
If the assigned id were truly the cause of the problem, I could use a generated id instead of the keyword as the primary key. This would avoid the NHibernate Insert/Update issue as it would effectively always insert. However, I still need to prevent duplicate keywords. With a unique index on the keyword column it will still throw an exception for a duplicate keyword even if the primary key is different.
Another Approach?
Perhaps the problem isn't really with NHibernate, but the way this is modeled. Unlike other areas of the application, this is more data-centric rather object-centric. It is nice that NHibernate makes it easy to read/write and eliminates the stored procedures. But the desire to simply write without regard to existing values doesn't fit well with the model of an object's identity model. Is there a better way to approach this?
I`m using
public IList<T> GetByExample<T>(T exampleInstance)
{
return _session.CreateCriteria(typeof(T))
.Add(Example.Create(exampleInstance))
.List<T>();
}
public void InsertOrUpdate<T>(T target)
{
ITransaction transaction = _session.BeginTransaction();
try
{
var res=GetByExample<T>(target);
if( res!=null && res.Count>0 )
_session.SaveOrUpdate(target);
else
_session.Save(target);
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
but FindByExample method returns all objects alike not objects with the exact ID what do you suggest ? since I have only object as parameter I don't have access to its specific ID field so I cannot use session.get(Object.class(), id);
Typically, NHibernate can rely on the unsaved-value to determine whether it should insert or create the entity. However, since you are assigning the ID, to NHibernate it looks like your entity has already been persisted. Therefore, you need to rely on versioning your object to let NHibernate know that it is a new object. See the following link for how to version your entity:
http://web.archive.org/web/20090831032934/http://devlicio.us/blogs/mike_nichols/archive/2008/07/29/when-flushing-goes-bad-assigned-ids-in-nhibernate.aspx
Use the session.SaveOrUpdate(object) method.
You can do
Obj j = session.get(Object.class(), id);
if (j != null)
session.merge(myObj);
else
session.saveOrUpdate(myObj);
Query objects where keyword = x, take FirstOrDefault. If it's null, Add new object, if it exists, update object that you got and call saveOrUpdate on it.
This worked for me:
Implementation
public void InsertOrUpdate<TEntity, TId>(TEntity entity) where TEntity : IIdentificableNh<TId>
{
var anyy = session.Get<TEntity>(entity.Id);
if (anyy != null)
{
session.Evict(anyy); //dispatch all data loaded, to allow updating 'entity' object.
session.Update(entity);
}
else
{
session.Save(entity);
}
session.Flush();
}
Entity
public class Caracteristica : IIdentificableNh<int>
{
public virtual int Id { get; set; }
public virtual string Descripcion { get; set; }
}
I had to create an interface (IIdentificableNh) that allows me to access the Id property value.
Usage example:
session.InsertOrUpdate<Caracteristica, int>(new Caracteristica { Id = 2, Descripcion = "Caracteristica2" });
call hibernate.saveOrUpdate() which will check if the object is in the database, update it if it is, and save (i.e. insert) it if it is not.