From 7bca8e5bc54c58d5713dc9f16d1f29e701acd599 Mon Sep 17 00:00:00 2001 From: chuanliang Date: Tue, 14 Feb 2023 16:32:42 +0800 Subject: [PATCH] init minimal-test-project to reproduce issue-2919: https://github.com/ebean-orm/ebean/issues/2919 --- pom.xml | 16 ++ .../LeadsWebActionDataCountAggregate1.java | 86 +++++++++ .../LeadsWebActionDataCountAggregate2.java | 75 ++++++++ .../example/domain/LeadsWebActionDataDao.java | 179 ++++++++++++++++++ 4 files changed, 356 insertions(+) create mode 100644 src/main/java/org/example/domain/LeadsWebActionDataCountAggregate1.java create mode 100644 src/main/java/org/example/domain/LeadsWebActionDataCountAggregate2.java create mode 100644 src/main/java/org/example/domain/LeadsWebActionDataDao.java diff --git a/pom.xml b/pom.xml index cd1b866..8623809 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,22 @@ + + + com.zaxxer + HikariCP + 3.3.1 + + + + + com.clickhouse + clickhouse-jdbc + 0.3.2-patch11 + + + + diff --git a/src/main/java/org/example/domain/LeadsWebActionDataCountAggregate1.java b/src/main/java/org/example/domain/LeadsWebActionDataCountAggregate1.java new file mode 100644 index 0000000..26e35a8 --- /dev/null +++ b/src/main/java/org/example/domain/LeadsWebActionDataCountAggregate1.java @@ -0,0 +1,86 @@ +package org.example.domain; + +import java.util.Date; +import javax.persistence.Column; +import javax.persistence.Entity; + + +/** + * for example + */ +@Entity +public class LeadsWebActionDataCountAggregate1 { + + @Column(name = "effective_action_type") + private String effectiveActionType; + + @Column(name = "effective_action_time") + private Date effectiveActionTime; + + /** + * this field must comply with this standard: if the sql's column is action_count, its conventional camel + * spelling is actionCount, if we use + * + * @Column(name = "action_count") + * private Integer count; + * + * the column mapping will not work, and we will see empty result record: effectiveActionType and + * effectiveActionTime will also be null. + */ + @Column(name = "action_count") + private Integer actionCount; + + /** + * get the value of effectiveActionType + * + * @return the value of effectiveActionType + */ + public String getEffectiveActionType() { + return effectiveActionType; + } + + /** + * set the value of the effectiveActionType + * + * @param effectiveActionType the value of effectiveActionType + */ + public void setEffectiveActionType(String effectiveActionType) { + this.effectiveActionType = effectiveActionType; + } + + /** + * get the value of effectiveActionTime + * + * @return the value of effectiveActionTime + */ + public Date getEffectiveActionTime() { + return effectiveActionTime; + } + + /** + * set the value of the effectiveActionTime + * + * @param effectiveActionTime the value of effectiveActionTime + */ + public void setEffectiveActionTime(Date effectiveActionTime) { + this.effectiveActionTime = effectiveActionTime; + } + + /** + * get the value of actionCount + * + * @return the value of actionCount + */ + public Integer getActionCount() { + return actionCount; + } + + /** + * set the value of the actionCount + * + * @param actionCount the value of actionCount + */ + public void setActionCount(Integer actionCount) { + this.actionCount = actionCount; + } +} diff --git a/src/main/java/org/example/domain/LeadsWebActionDataCountAggregate2.java b/src/main/java/org/example/domain/LeadsWebActionDataCountAggregate2.java new file mode 100644 index 0000000..f0a6a0c --- /dev/null +++ b/src/main/java/org/example/domain/LeadsWebActionDataCountAggregate2.java @@ -0,0 +1,75 @@ +package org.example.domain; + +import java.util.Date; +import javax.persistence.Column; + + +/** + * for example + */ +public class LeadsWebActionDataCountAggregate2 { + + @Column(name = "effective_action_time") + private Date effectiveActionTime; + + + @Column(name = "effective_action_type") + private String effectiveActionType; + + @Column(name = "action_count") + private Integer actionCount; + + /** + * get the value of effectiveActionTime + * + * @return the value of effectiveActionTime + */ + public Date getEffectiveActionTime() { + return effectiveActionTime; + } + + /** + * set the value of the effectiveActionTime + * + * @param effectiveActionTime the value of effectiveActionTime + */ + public void setEffectiveActionTime(Date effectiveActionTime) { + this.effectiveActionTime = effectiveActionTime; + } + + /** + * get the value of effectiveActionType + * + * @return the value of effectiveActionType + */ + public String getEffectiveActionType() { + return effectiveActionType; + } + + /** + * set the value of the effectiveActionType + * + * @param effectiveActionType the value of effectiveActionType + */ + public void setEffectiveActionType(String effectiveActionType) { + this.effectiveActionType = effectiveActionType; + } + + /** + * get the value of actionCount + * + * @return the value of actionCount + */ + public Integer getActionCount() { + return actionCount; + } + + /** + * set the value of the actionCount + * + * @param actionCount the value of actionCount + */ + public void setActionCount(Integer actionCount) { + this.actionCount = actionCount; + } +} diff --git a/src/main/java/org/example/domain/LeadsWebActionDataDao.java b/src/main/java/org/example/domain/LeadsWebActionDataDao.java new file mode 100644 index 0000000..edc0845 --- /dev/null +++ b/src/main/java/org/example/domain/LeadsWebActionDataDao.java @@ -0,0 +1,179 @@ +package org.example.domain; + + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import io.ebean.EbeanServer; +import io.ebean.EbeanServerFactory; +import io.ebean.Query; +import io.ebean.config.ServerConfig; +import java.util.List; + +/** + + * + * @author magicliang + * + */ + +public class LeadsWebActionDataDao { + private static final String RAW_SQL = "select\n" + + " effective_action_type,\n" + + " CAST(min_action_time2 AS DATE) as effective_action_time,\n" + + " count(*) as action_count\n\n" + + " from\n" + + " (\n" + + " select\n" + + " effective_action_type,\n" + + " sign_id,\n" + + " min(action_time2) as min_action_time2\n" + + " from\n" + + " (\n" + + " select\n" + + " sign_id,\n" + + " action_time2,\n" + + " CASE\n" + + " WHEN final_action_type = 'wx_1166' THEN '405'\n" + + " ELSE final_action_type\n" + + " END AS effective_action_type\n" + + " from\n" + + " leads_web_action_data\n" + + " where\n" + + " action_time2 between :actionTimeEnd\n" + + " and :actionTimeEnd\n" + + " and all_id = :all_id\n" + + " AND (\n" + + " OR og in (\n" + + " 6,\n" + + " 104,\n" + + " 105,\n" + + " 427,\n" + + " 204,\n" + + " 108,\n" + + " 302,\n" + + " 10000,\n" + + " 402,\n" + + " 403,\n" + + " 405,\n" + + " 502,\n" + + " 504,\n" + + " 409,\n" + + " 316,\n" + + " 412\n" + + " )\n" + + " OR og is null\n" + + " )\n" + + " order by\n" + + " action_time2 desc\n" + + " )\n" + + " group by\n" + + " effective_action_type,\n" + + " sign_id\n" + + " )\n" + + " group by\n" + + " effective_action_type,\n" + + " CAST(min_action_time2 AS DATE)\n" + + " "; + + + /** + * issue 1: + * + * this query will return [{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{} + * ,{},{},{},{},{},{},{}] + * condition to reproduce the empty records: + * + * for column action_count, we can use actionCount as member field, but if we want another name for business + * purpose for examle "count", instead of actionCount as conventional name: + * + * @Column(name = "action_count") + * private Integer count; + * + * + * the column mapping will not work, and the whole result will be empty, that means column mapping annotation is + * useless. + * + * @return list with empty records + */ + public List findNative() { + Query basicQuery = + getServer().findNative(LeadsWebActionDataCountAggregate1.class, + RAW_SQL) + .setParameter("all_id", 12345) + .setParameter("actionTimeBegin", "2022-11-11 00:00:00") + .setParameter("actionTimeEnd", "2022-11-11 23:59:59"); + return basicQuery.findList(); + } + + /** + * issue2: + * as we can see the raw sql, the result of SQL is: + * + * effective_action_type, effective_action_time, action_count + * + * They can be mapped to java type: String, Date, Integer + * + * If we declare our instance member like this(the order is Integer): + * + * + * @return an exception will be thrown, there will be no result + * @Column(name = "effective_action_time") + * * private Date effectiveActionTime; + * @Column(name = "effective_action_type") + * * private String effectiveActionType; + * @Column(name = "action_count") + * private Integer actionCount; + * + * + * We declare effectiveActionTime before effectiveActionType, we want the ebean to map column result of + * effective_action_time to effectiveActionTime, and we get an exception here: + * + * javax.persistence.PersistenceException: Query threw SQLException:Text '204' could not be parsed at index + * 0 + * Query was: select + * + * the 204 is result for column effective_action_type, as the first column result. ebean tries to map it + * to first instance member because it is first column in result set, ignoring that the mapped variable + * for effective_action_type accoriding to mapping is the second member variable. + * + * So the ebean actual behavior is, when we use findDto api, the sequence of member is important, the + * mapping annotation is useless. + */ + public List findDto() { + return getServer().findDto(LeadsWebActionDataCountAggregate2.class, RAW_SQL) + .setParameter("all_id", 12345) + .setParameter("actionTimeBegin", "2022-11-11 00:00:00") + .setParameter("actionTimeEnd", "2022-11-11 23:59:59") + .findList(); + } + + static public synchronized EbeanServer getServer() { + HikariConfig config = new HikariConfig(); + config.setDriverClassName("com.clickhouse.jdbc.ClickHouseDriver"); + config.setJdbcUrl("jdbc:clickhouse://localhost:8123/myclickhouse-db"); + config.setUsername("myclickhouse.username"); + config.setPassword("myclickhouse.passwd"); + config.setConnectionTimeout(30000L); + config.setMinimumIdle(5); + config.setIdleTimeout(30000L); + config.setMaximumPoolSize(50); + config.setMaxLifetime(180000L); + config.setValidationTimeout(5000L); + config.setConnectionTestQuery("select 1"); + config.setRegisterMbeans(true); + config.setConnectionInitSql(null); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "250"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + + + ServerConfig serverConfig = new ServerConfig(); + serverConfig.setName("clickhouseserver"); + serverConfig.setDataSource(new HikariDataSource(config)); + serverConfig.setRegister(true); + serverConfig.setDefaultServer(false); + serverConfig.addPackage("leads.models"); + EbeanServer ebeanServer = EbeanServerFactory.create(serverConfig); + return ebeanServer; + } +}