QueryDSL and date diff - mysql

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();

Related

symfony doctrine will not default a boolean to 0

I have tried in several ways to have symfony default a boolean to 0 rather than null (as null gives me a database level error upon flush).
An exception occurred while executing a query: SQLSTATE[23000]:
Integrity constraint violation: 1048 Column 'auto_created' cannot be
null
This made no difference:
/**
* #ORM\Column(type="boolean", options={"default":"0"})
*/
private $autoCreated;
Some logic i the setter made no difference either
public function setAutoCreated(bool $autoCreated): self
{
if is_null($autoCreated) {
$autoCreated = 0;
}
$this->autoCreated = $autoCreated;
return $this;
}
As well as
public function setAutoCreated(bool $autoCreated): self
{
if is_null($autoCreated) {
$autoCreated = false;
}
$this->autoCreated = $autoCreated;
return $this;
}
Database looks like this
I am clearly missing something...?
Sure I can do a simple $user->setAutoCreated(false); everywhere I create this entity, but I don't get why I should have to 😎
Depending on the version of PHP you're using, you should be able to do something like this in your entity class:
/**
* #ORM\Column(type="boolean")
*/
private $autoCreated = false;
Whenever an instance of this class is created, $autoCreated will be set to false. So when you try to persist the object, it'll have a default value of false and Doctrine will set the field to 0.
Alternatively, you can explicitly set $autoCreated to false in your constructor:
public function __construct()
{
$this->autoCreated = false;
}
Note that you can only use the first approach for simple, built-in PHP types or constants. For more complicated objects (e.g., a Doctrine ArrayCollection) you'll need to use the constructor approach.

PESSIMESTIC LOCK is not working with Spring Data accessing MySQL

I am using Spring Boot to build a scheduled-job data processing application. The main logic would be in a scheduled job that takes a batch of records and process them. I should be running 2 instances of the application that should not pick the same record twice. I tried to utilize the PESSIMISTIC LOCK with NO WAIT to resolve any records selection conflict.
Things are not working as expected. Both instances are picking the same records, although I was expecting only one instance to lock and process a few records and the other instance skip what was locked by the first instance.
Spring Boot version: 2.2.4.RELEASE
Database: MySQl
First I tried using the #Lock and #QueryHint annotations:
#Lock(value = LockModeType.PESSIMISTIC_WRITE) // adds 'FOR UPDATE' statement
#QueryHints(value={#QueryHint(name = "javax.persistence.lock.timeout", value = LockOptions.SKIP_LOCKED+"")})
Page<Transaction> findByStatus(String status, Pageable pageable);
Even with WAIT_FOREVER, there is no change in behavior as if #QueryHints are totally ignored..
The other option I tried is using NativeQuery:
#Query(value ="select * from transaction t where t.status = ?1 limit ?2 for update SKIP LOCKED",
countQuery="select count(*) from transaction t where t.status = ?1",
nativeQuery = true)
List<Transaction> findByStatusNQ(String status, Integer pageSize);
Same behavior. No locking, both app instances are selecting the same set of data
This is the defined entity:
#Entity
public class Transaction {
#Id
private Long id;
private String description;
private String status;
private String managedBy;
#Temporal(TemporalType.TIMESTAMP)
private Date manageDate;
...
}
The caller service component is annotated with #Transactional to enforce creating new transaction for each execution:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public List<Transaction> updateTrxStatus(String oldStatus,String newStatus){
List<Transaction> trxs = this.executeUsingNQ(oldStatus);
if(trxs.size()>0) {
logger.info( "Start updating Data");
trxs.forEach(transaction -> {
transaction.setStatus(newStatus);
transaction.setManagedBy(instanceName);
transaction.setManageDate(new Date(System.currentTimeMillis()));
});
}else{
logger.info(" Nothing to process");
}
return trxs;
}
#Transactional(propagation = Propagation.REQUIRED)
public List<Transaction> executeUsingNQ(String oldStatus){
List<Transaction> trxs = trxRepo.findByStatusNQ(oldStatus,2);
return trxs;
}
#Transactional(propagation = Propagation.REQUIRED)
public List<Transaction> executeWithPage(String oldStatus){
Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
Page<Transaction> trxs = trxRepo.findByStatus(oldStatus, firstPageWithTwoElements);
return trxs.getContent();
}
Hopefully someone can help identifying whether there is some coding issue or missing coniguration!!!!
It runs that the issue was caused by using an incorrect Dialect with MySql. That version of Dialect "MySQLDialect" assumes "MyISAMStorageEngine" as a default storage engine while creating tables. That engine does not support any type of transactions.
The only storage engine that supports transactions is "InnoDB" which is being selected as the default choice when using other Dialects like "MySQL55Dialect", "MySQL57Dialect" or "MySQL8Dialect"

Doctrine findBy boolean field returns no results

Recently, a piece of code stopped working. I haven't made any changes to it so I don't know why.
Here's the code:
$invites = $this->vault_em->getRepository('AppBundle:Invite\LocalInvite')->findBy([
'active' => true,
]);
Now, it's returning an empty array, even though there are LocalInvite records with active = 1.
Here are the doctrine mappings:
/**
* #ORM\Entity
* #ORM\Table(name="invite")
*/
class LocalInvite extends Invite {
//...
}
/** #ORM\MappedSuperclass */
abstract class Invite implements \JsonSerializable {
/** #ORM\Column(type="boolean", options={"default": true}) */
protected $active;
//...
}
To debug, I copied the underlying MySQL query that Doctrine is executing from the debug logs:
SELECT t0.id AS id_1, t0.email AS email_2, t0.active AS active_3, t0.location AS location_4, t0.cohort_leadership AS cohort_leadership_5, t0.timezone AS timezone_6, t0.date_record_created AS date_record_created_7, t0.date_record_deleted AS date_record_deleted_8, t0.date_restart AS date_restart_9, t0.date_start_invite AS date_start_invite_10, t0.employee_id AS employee_id_11, t0.first_name AS first_name_12, t0.corporate_client_name AS corporate_client_name_13, t0.client_id AS client_id_14, t0.landing_page_url AS landing_page_url_15, t0.user_id AS user_id_16, t0.recipient_id AS recipient_id_17 FROM invite t0 WHERE t0.active = true;
When I plug that query into a MySQL IDE, it returns results.
Why does the findBy return no results?
try to change 'AppBundle:Invite\LocalInvite' by LocalInvite::class

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 ^;

Hibernate Regexp 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)"),
})