Hibernate Regexp MySQL - mysql

I ask this question to show how MySQL and Hibernate work each other with Regular Expressions.
The problem:
SELECT * FROM table WHERE regexp column '\d'
Solution:
Go to my answer.
Hope this helps.

Basically, to use MySQL regexp function in Hibernate we need to create a "SQLFunctionTemplate".
Now, how to do it:
First: Create a class called "AppMySQLDialect" and extends from MySQLDialect then override the empty constructor and finally register the regexp function:
public class AppMySQLDialect extends MySQLDialect {
public AppMySQLDialect() {
super();
/**
* Function to evaluate regexp in MySQL
*/
registerFunction("regexp", new SQLFunctionTemplate(Hibernate.INTEGER, "?1 REGEXP ?2"));
}
}
Ok, now lets use it as follow:
FROM Entity E WHERE regexp(E.string2evaluate, '\d') = 1
Create your HibernateQuery and execute.

String range = "ABCD";
List<HRTrainee> hrTrainees =
(List<HRTrainee>)sessionFactory.getCurrentSession().createCriteria(HRTrainee.class)
.add(Restrictions.sqlRestriction("name REGEXP '^["+range+"]'")).list();
return hrTrainees;

REGEXP is treated as a keyword in MySQL. User can use REGEXP in hibernate filter by registering the keyword.
Create a class 'CustomMySQL5InnoDBDialect' extending MySQL5InnoDBDialect and register the keyword as follows :
public class CustomMySQL5InnoDBDialect extends MySQL5InnoDBDialect {
public CustomMySQL5InnoDBDialect() {
super();
/* register regexp keyword */
registerKeyword("regexp");
}
}
Then change the hibernate-dialect property in persistence.xml as
<property name="hibernate.dialect" value="com.CustomMySQL5InnoDBDialect"/>
User can use the regexp in hibernate filter as follows
#Filters(value = { #Filter(name="applyStudentFilter",condition="id in (select s.id from student s WHERE s.address REGEXP :addressValue)"),
})

Related

How to define the descriminator column as ENUM for Class Table Inheritance in Doctrine's?

I want to implement the Class Table Inheritance:
/**
* Foo
*
* #ORM\Table(name="foos", ...)
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "bar" = "Bar",
* "buz" = "Buz"
* })
*/
abstract class Foo
{
...
}
Since I run the "code first" approach, the database is generated by Doctrine:
$ bin/console doctrine:migrations:diff
$ bin/console doctrine:migrations:migrate
The discriminator column foos.type gets the type VARCHAR(255). I want it to get an ENUM instead.
How to define the annotation for the entity class for getting an ENUM discriminator?
It works with columnDefinition="ENUM('bar', 'buz')":
/**
* Foo
*
* #ORM\Table(name="foos", ...)
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string", columnDefinition="ENUM('bar', 'buz')")
* #ORM\DiscriminatorMap({
* "bar" = "Bar",
* "buz" = "Buz"
* })
*/
abstract class Foo
{
...
}
Unfortunately it causes an annoying side effect (s. also here): The migration mechanism of Doctrine seems to handle ENUMs incorrectly. The doctrine:migrations:diff command creates a migration like this:
final class Version20180619205625 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->addSql('ALTER TABLE foos CHANGE type `type` ENUM(\'bar\', \'buz\')');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE tasks CHANGE `type` type VARCHAR(255) DEFAULT NULL COLLATE utf8mb4_unicode_ci');
}
}
I execute it, the type column become an ENUM. But a new executing of doctrine:migrations:diff creates a migration with the same content again... Means, Doctrine "thinks", the column is still VARCHAR.
Bit late answer, but the solution is to register a custom mapping type which will actually use ENUM on database side. Most simply:
class YourType extends Type
{
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
return 'ENUM(\'one\', \'two\')';
}
}
register it in your Doctrine config under some your_type name and use:
#ORM\DiscriminatorColumn(name="type", type="your_type")
This field will be NOT NULL by default. In case of mysql, it'll use first value from ENUM as default, like a "implicit default"

Hibernate select after insertion

I'm having an issue with inserting new rows into my MySQL database. I'm using Spring Boot with Spring Boot Data JPA.
Since MySQL doesn't support sequences, I decided to try and make my own sequence generator table. This is basically what I've done.
I created a sequences table that uses an auto increment field (used as my id's for my tables).
Created a function, sequences_nextvalue() which inserts into the sequences table and returns the new auto incremented id.
I then created triggers on each table that get triggered before insertion and replaces the id field with the result of calling sequences_nextvalue().
So this is working fine when inserting new rows. I'm getting unique ids across all tables. The issue I'm having is with my JPA entities.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractBaseClass {
#Id
private Integer id = -1;
...
}
#Entity
public class ConcreteClass1 extends AbstractBaseClass {
...
}
#Entity
public class ConcreteClass2 extends AbstractBaseClass {
...
}
I want to be able to query from the abstract base class so I've placed my #Id column in that class and used #Entity with InheritanceType.TABLE_PER_CLASS. I've also initialized the id to -1 since an id is required to call save() from my spring crud repository.
After calling the save() function of my Spring data CrudRepository, the -1 for id properly gets replaced by the MySQL trigger but the resulting entity returned by save() doesn't return with the new id but instead retains the -1. After looking at the SQL logs, a select statement is not being called after insertion to get the new id but instead the original entity is being returned.
Is it possible to force Hibnerate to re-select the entity after insertion to get the new id when you're not using #GeneratedValue?
Any help is greatly appreciated.
Just wanted to provide an update on this question. Here is my solution.
Instead of creating MySQL TRIGGER's to replace the id on INSERT, I created a Hibernate IdentifierGenerator which executes a CallableStatement to get and return a new id.
My abstract base class now looks like this.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractBaseClass {
#Id
#GenericGenerator(name="MyIdGenerator", strategy="com.sample.model.CustomIdGenerator" )
#GeneratedValue(generator="MyIdGenerator" )
private Integer id;
...
}
and my generator looks like this.
public class CustomIdGenerator implements IdentifierGenerator {
private Logger log = LoggerFactory.getLogger(CustomIdGenerator.class);
private static final String QUERY = "{? = call sequence_nextvalue()}";
#Override
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
Integer id = null;
try {
Connection connection = session.connection();
CallableStatement statement = connection.prepareCall(QUERY);
statement.registerOutParameter(1, java.sql.Types.INTEGER);
statement.execute();
id = statement.getInt(1);
} catch(SQLException e) {
log.error("Error getting id", e);
throw new HibernateException(e);
}
return id;
}
}
And just for reference
The sequences table.
CREATE TABLE sequences (
id INT AUTO_INCREMENT PRIMARY KEY,
thread_id INT NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP
) ^;
The sequence_nextvalue function
CREATE FUNCTION sequence_nextvalue()
RETURNS INTEGER
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN
DECLARE nextvalue INTEGER;
INSERT INTO sequences (thread_id) VALUE (CONNECTION_ID());
SELECT id FROM sequence_values ORDER BY created DESC LIMIT 1 INTO nextvalue;
RETURN nextvalue;
END ^;

Yii2 - replacement for beforeFind to optimize nested views in MySql

Yii1 used to have beforeFind method in which you could modify the query or whatever else you might want to do.
In Yii2 the suggested alternative is to use the modelQuery solution for example
class MyModel extends \yii\db\ActiveRecord
{
public static function find()
{
return new MyModelQuery(get_called_class());
}
/* ... */
}
and
class MyModelQuery extends \yii\db\ActiveQuery
{
public function init( )
{
/** do something here **/
}
}
But how do I pass or reference MyModel within MyModelQuery?
For example:-
class MyModelQuery extends \yii\db\ActiveQuery
{
public function init( )
{
$sql = "SET #variable = {$MyModel->variable1}";
}
}
EDIT
For completeness, I've added a use case to help others in future.
I have nested views with group by's running under MySql and it runs VERY badly.
In my case, I have orders, order-items and order-item-fees tables, each one-to-many to the next and I want to sum the order totals. I have nested view, one at each level to sum to the level above, but at the order-item and order-item-fee levels MySql is grouping the whole table first (I cannot use algorithm=merge as I have a GROUP BY).
I'm implementing the Pushdown method where you define a SQL variable to use in sub-views to narrow down the search as outlined here: http://code.openark.org/blog/mysql/views-better-performance-with-condition-pushdown
and also here
https://www.percona.com/blog/2010/05/19/a-workaround-for-the-performance-problems-of-temptable-views/
In this way, if I can add a 'WHERE order_id=' to the where clause of the two sub-views, I reduce a 3.5 second query down to 0.003 second query.
So using, Salem's suggestion below, I can execute a SQL statement 'SET #order_id=1234' before my query, which is then picked up in the order-item and order-item-fee views using a function. Note: this is connection specific, so no danger of collisions between sessions.
A bit convoluted but fast.
It would be interesting, though, to see a performance comparison between SQL and looping in PHP perhaps....
EDIT 2
In fact, you normally use find() as a static method, so there is no way of using $this->order_id, so I changed this to over-ride the findOne method
public static function findOne( $orderId )
{
if ( isset($orderId) )
{
$sql = "SET #orderId='{$orderId}'";
Yii::$app->db->createCommand($sql)->execute();
}
return parent::findOne( $orderId );
}
I also use this view with other searches, so in the view I need to check whether the orderId is set or not ...
where (
CASE
WHEN ( NOT isnull( get_session_orderId() ) )
THEN `order`.order_id = get_session_cartref()
ELSE `order`.order_id LIKE '%'
END
)
About how to involve an ActiveQuery class check my answer here:
Yii2 : ActiveQuery Example and what is the reason to generate ActiveQuery class separately in Gii?
But if what you are trying to do doesn't require building named scopes then you may simply override the find method by something like this:
public static function find()
{
return parent::find()->where(['variable' => 'some value']);
}

QueryDSL and date diff

I'm trying to compute user age with a date difference using QueryDSL.
QPlayer $ = QPlayer.player;
BooleanBuilder builder = new BooleanBuilder();
builder.and(Expressions.dateOperation(Integer.class, Ops.DateTimeOps.DIFF_YEARS, Expressions.currentDate(), $.birthDate).between(3, 5));
playerRespository.findAll(builder);
But fail with this error
Hibernate: select player0_.user_id as id1_31_, player0_1_.user_birthdate as user_bir2_31_, player0_1_.user_register_date as user_reg3_31_, player0_1_.user_delete_date as user_del4_31_, player0_1_.user_email as user_ema5_31_, player0_1_.user_first_name as user_fir6_31_, player0_1_.user_last_name as user_las7_31_, player0_1_.user_login as user_log8_31_, player0_1_.user_password as user_pas9_31_, player0_1_.user_status as user_st10_31_, player0_.player_description as player_d1_20_, player0_.player_height as player_h2_20_, player0_.player_picture as player_p3_20_, player0_.player_role as player_r4_20_, player0_.player_weight as player_w5_20_ from players player0_ inner join users player0_1_ on player0_.user_id=player0_1_.id where (diff_years(player0_1_.user_birthdate, current_date) between ? and ?) and (lower(player0_1_.user_first_name) like ? escape '!')
2015-07-19 14:22:16,881 [main] ERROR: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - FUNCTION xxx.diff_years does not exist
This errors occurs using both MYSQL database or HSQL.
What's wrong with that code?
Thanks
diff_years is unfortunately not yet supported in Querydsl JPA
How to customize MYSQL dialect with queryDsl. I show how to use datediff for select statement.
Custom query request (calculate difference between now and createdDate):
public List<MyDto> get() {
JPAQuery<HotelShortDto> query = new JPAQuery<>(em, MySQLJPQLTemplates.DEFAULT)
.select(Projections.constructor(MyDto.class,
Expressions.dateOperation(Integer.class, Ops.DateTimeOps.DIFF_MINUTES, Expressions.currentDate(), myEntity.createdDate),
))
.from(myEntity)
return query.fetch();
}
In MYSQL function to get difference in minutes is TIMESTAMPDIFF (for H2 db datediff)
Configuration:
public class MySQLJPQLTemplates extends JPQLTemplates {
public static final MySQLJPQLTemplates DEFAULT = new MySQLJPQLTemplates();
public MySQLJPQLTemplates() {
this(DEFAULT_ESCAPE);
add(Ops.DateTimeOps.DIFF_MINUTES, "TIMESTAMPDIFF(MINUTE,{0},{1})");
}
public void reconfigureForH2() {
add(Ops.DateTimeOps.DIFF_MINUTES, "datediff(MINUTE,{0},{1})");
}
public MySQLJPQLTemplates(char escape) {
super(escape);
}
}
Also TIMESTAMPDIFF is not standart function for hibernate, so registration is needed
public class CustomMySQLDialect extends MySQL57Dialect {
public CustomMySQLDialect() {
super();
registerFunction("TIMESTAMPDIFF", new StandardSQLFunction("TIMESTAMPDIFF"));
}
}
and application.yaml
...
spring.jpa.database-platform=com.my.project.CustomMySQLDialect
...
For testing call before execute query
MySQLJPQLTemplates.DEFAULT.reconfigureForH2();

How to use database specific functions in Laravel Eloquent ORM for modular usage

Im developing a project which uses ORM to make project run on every database system as much as we can.
Project uses postgresql right now. Im wondering how to use database specific functions without losing ORM modularity.
For example:
I have to use "extract" function for one query like so;
DELETE FROM tokens AS t WHERE (extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())
If i want to use model class to achieve this. Soon or late i need to write extract function where clause in raw format
Tokens::whereRaw('(extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())')->get();
If i use query builder
DB::table('tokens')->whereRaw('(extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())')->select()->get();
Same things happens
I need something like when i use postgresql ORM need to use EXTRACT() function or when i use mysql ORM need to use UNIX_TIMESTAMP() function
What the ways i can use to achieve this ?
This could go in the respective drivers, but Taylor Otwell's view on driver-specific functions is, that you simply should use raw statements, just like you do.
However in Eloquent you can pretty easily do it yourself:
// BaseModel / trait / builder macro or whatever you like
public function scopeWhereUnix($query, $col, $operator = null, $value = null)
{
if (func_num_args() == 3)
{
list($value, $operator) = array($operator, '=');
}
switch (class_basename($query->getQuery()->getConnection()))
{
case 'MySqlConnection':
$col = DB::raw("unix_timestamp({$col})");
break;
case 'PostgresConnection':
$col = DB::raw("extract(epoch from {$col})");
break;
}
$query->where($col, $operator, $value);
}
Now you can do this:
Tokens::whereUnix('created_at', 'value')->toSql();
// select * from tokens where unix_timestamp(created_at) = 'value'
// or
// select * from tokens where extract(epoch from created_at) = 'value'
You have a bit more complex condition, but you still can achieve that with a little bit of hack:
Tokens::whereUnix(DB::raw('created_at) + (expires', '<', time())->toSql();
// select * from tokens where unix_timestamp(created_at) + (expires) < 12345678
// or
// select * from tokens where extract(epoch from created_at) + (expires) < 12345678
Unfortunately Query\Builder (DB::table(..)) is not that easy to extend - in fact it is not extendable at all, so you would need to swap it with your own Builder class, what is rather cumbersome.
Take this logic out of the models.
Create a repository for Postgres, let's call it PostgresTokenRepository. The constructor of this repository should look like...
<?php
class PostgresTokenRepository implements TokenRepositoryInterface
{
protected $token;
public function __construct(Token $token)
{
$this->token = $token;
}
public function getTokens()
{
return $this->token->whereRaw('(extract(epoch from t.created_at) + t.expires) < extract(epoch from NOW())')->get();
}
}
And you will need an interface... TokenRepositoryInterface
interface TokenRepositoryInterface
{
public function getTokens();
}
Now you should be all set as far as the repository goes. If you need to do a MySQL implementation, just create a MysqlTokenRepository which will look similar except the getTokens() function would use UNIX_TIMESTAMP().
Now you need to tell Laravel that when you are looking for an implementation of TokenRepositoryInterface, it should return PostgresTokenRepository. For that, we will need to create a service provider.
<?php
class UserServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function register()
{
$this->app->bind('TokenRepositoryInterface', 'PostgresTokenRepository');
}
}
And now the only thing left to do is add this Service Provider to the service providers array in config/app.php.
Now whenever you need this repository in your controllers, you can have them automatically injected. Here is an example...
class TokenController extends BaseController
{
protected $token;
public function __construct(TokenRepositoryInterface $token)
{
$this->token = $token;
}
public function index()
{
$tokens = $this->token->getTokens();
return View::make('token.index')->with('tokens', $tokens);
}
}
The purpose for doing it this way is when you want to start using the MySQL implementation, all you have to do is modify the service provider to return MysqlTokenRepository instead of PostgresTokenRepository. Or if you want to write a new implementation all together, it will all be possible without having to change production code. If something doesn't work, simply change that one line back to PostgresTokenRepository.
One other benefit that sold me is this gives you the capability of keeping your models and controllers very light and very testable.
I ended up creating a global scope. Created a trait like ExpiresWithTimestampsTrait that contains the logic for whereExpires scope. The scope does adding where clause that specific to database driver.
public function scopeWhereExpired($query)
{
// Eloquent class is my helper for getting connection type based on the #jarek's answer
$type = Eloquent::getConnectionType($this);
switch ($type) {
case Eloquent::CONNECTION_POSTGRESS:
return $query->whereRaw("(round(extract(epoch from (created_at)) + expires)) < round(extract(epoch from LOCALTIMESTAMP))");
break;
default:
break;
}
}
So i just need to use that trait on the model. I need to add just an "case" clause to whereExpires scope for support mysql with where clause in the future when i start using mysql
Thanks to everybody!