bin.yin created IGNITE-20024:
--------------------------------
Summary: When MySQL is used as the underlying storage, an
exception is thrown "Can not set java.time.LocalDateTime field
org.apache.ignite.examples.model.Person.createdTime to java.sql.Timestamp"
Key: IGNITE-20024
URL: https://issues.apache.org/jira/browse/IGNITE-20024
Project: Ignite
Issue Type: Bug
Components: persistence
Reporter: bin.yin
Assignee: bin.yin
h3. Question:
When I use IgniteCache.get(key) to get the corresponding entity, ignite throws
the following error "Can not set java.time.LocalDateTime field
org.apache.ignite.examples.model.Person.createdTime to java.sql .Timestamp"
h3. Conditions and usage:
# This code run in 'ignite-examples' module;
# In ignite 2.14.0, I use H2 as the underlying storage;
# In {{{}CacheJdbcPojoStoreFactory{}}}, I configured the mapping between the
{{datetime type}} of the database and JDK8's {{{}LocalDateTime{}}}:
{{new JdbcTypeField(Types.TIMESTAMP, "created_time", LocalDateTime.class,
"createdTime")}}
h3. Problem analysis:
By default, {{CacheJdbcPojoStore}} uses
{{org.apache.ignite.cache.store.jdbc.JdbcTypesTransformer#getColumnValue}} to
parse jdbc's ResultSet.
For the {{LocalDateTime}} type of JDK8, {{java.sql.ResultSet#getObject(int)}}
is used to obtain the value of the specified column (here it is read as
{{{}java.sql.Timestamp{}}}). When the object is finally constructed, the type
is inconsistent Causes {{sun.reflect.FieldAccessor#set}} assignment to fail.
*Java Code:*
CacheJdbcPojoStore.java:
{code:java}
package org.apache.ignite.examples.datagrid.store.auto;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory;
import org.apache.ignite.cache.store.jdbc.JdbcType;
import org.apache.ignite.cache.store.jdbc.JdbcTypeField;
import org.apache.ignite.cache.store.jdbc.dialect.H2Dialect;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.examples.model.Person;
import org.apache.ignite.examples.util.DbH2ServerStartup;
import org.h2.jdbcx.JdbcConnectionPool;
import javax.cache.configuration.Factory;
import javax.sql.DataSource;
import java.sql.Types;
import java.time.LocalDateTime;
/**
* To start the example, you should:
* <ul>
* <li>Start H2 database TCP server using {@link DbH2ServerStartup}.</li>
* <li>Start example using {@link CacheAutoStoreExample}.</li>
* </ul>
* <p>
*/
public class CacheJdbcPojoStore {
private static final String CACHE_NAME =
CacheJdbcPojoStore.class.getSimpleName();
public static void main(String[] args) throws Exception {
DbH2ServerStartup.populateDatabase();
try (Ignite ignite = Ignition.start("example-ignite.xml")) {
System.out.println();
System.out.println(">>> Cache store example started.");
try (IgniteCache<Long, Person> cache =
ignite.getOrCreateCache(cacheConfiguration())) {
cache.loadCache(null, Long.class.getName(), "SELECT * FROM
PERSON WHERE ID = 3");
System.out.println(cache.get(3L));
}
finally {
ignite.destroyCache(CACHE_NAME);
}
}
}
private static CacheConfiguration<Long, Person> cacheConfiguration() {
CacheConfiguration<Long, Person> cacheCfg = new
CacheConfiguration<>(CACHE_NAME);
cacheCfg.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
// Configure JDBC store.
CacheJdbcPojoStoreFactory<Long, Person> factory = new
CacheJdbcPojoStoreFactory<>();
factory.setDialect(new H2Dialect());
factory.setDataSourceFactory((Factory<DataSource>) () -> {
return
JdbcConnectionPool.create("jdbc:h2:tcp://localhost/mem:ExampleDb", "sa", "");
});
JdbcType jdbcType = new JdbcType();
jdbcType.setCacheName(CACHE_NAME);
jdbcType.setDatabaseTable("PERSON");
jdbcType.setKeyType(Long.class);
jdbcType.setValueType(Person.class);
jdbcType.setKeyFields(new JdbcTypeField(Types.BIGINT, "id", Long.class,
"id"));
jdbcType.setValueFields(
new JdbcTypeField(Types.BIGINT, "id", Long.class, "id"),
new JdbcTypeField(Types.VARCHAR, "first_name", String.class,
"firstName"),
new JdbcTypeField(Types.VARCHAR, "last_name", String.class,
"lastName"),
new JdbcTypeField(Types.TIMESTAMP, "created_time",
LocalDateTime.class, "createdTime")
);
factory.setTypes(jdbcType);
cacheCfg.setCacheStoreFactory(factory);
cacheCfg.setIndexedTypes(Long.class, Person.class);
return cacheCfg;
}
}
{code}
Person.java:
{code:java}
package org.apache.ignite.examples.model;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.ignite.cache.affinity.AffinityKey;
import org.apache.ignite.cache.query.annotations.QuerySqlField;
import org.apache.ignite.cache.query.annotations.QueryTextField;
/**
* Person class.
*/
public class Person implements Serializable {
/** */
private static final AtomicLong ID_GEN = new AtomicLong();
/** Name of index by two fields (orgId, salary). */
public static final String ORG_SALARY_IDX = "ORG_SALARY_IDX";
/** Person ID (indexed). */
@QuerySqlField(index = true)
public Long id;
/** Organization ID (indexed). */
@QuerySqlField(index = true, orderedGroups = @QuerySqlField.Group(name =
ORG_SALARY_IDX, order = 0))
public Long orgId;
/** First name (not-indexed). */
@QuerySqlField
public String firstName;
/** Last name (not indexed). */
@QuerySqlField
public String lastName;
@QuerySqlField
public LocalDateTime createdTime;
/** Resume text (create LUCENE-based TEXT index for this field). */
@QueryTextField
public String resume;
/** Salary (indexed). */
@QuerySqlField(index = true, orderedGroups = @QuerySqlField.Group(name =
ORG_SALARY_IDX, order = 1))
public double salary;
/** Custom cache key to guarantee that person is always colocated with its
organization. */
private transient AffinityKey<Long> key;
/**
* Default constructor.
*/
public Person() {
// No-op.
}
/**
* Constructs person record.
*
* @param org Organization.
* @param firstName First name.
* @param lastName Last name.
* @param salary Salary.
* @param resume Resume text.
*/
public Person(Organization org, String firstName, String lastName, double
salary, String resume) {
// Generate unique ID for this person.
id = ID_GEN.incrementAndGet();
orgId = org.id();
this.firstName = firstName;
this.lastName = lastName;
this.createdTime = LocalDateTime.now();
this.salary = salary;
this.resume = resume;
}
/**
* Constructs person record.
*
* @param id Person ID.
* @param orgId Organization ID.
* @param firstName First name.
* @param lastName Last name.
* @param salary Salary.
* @param resume Resume text.
*/
public Person(Long id, Long orgId, String firstName, String lastName,
double salary, String resume) {
this.id = id;
this.orgId = orgId;
this.firstName = firstName;
this.lastName = lastName;
this.createdTime = LocalDateTime.now();
this.salary = salary;
this.resume = resume;
}
/**
* Constructs person record.
*
* @param id Person ID.
* @param firstName First name.
* @param lastName Last name.
*/
public Person(Long id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.createdTime = LocalDateTime.now();
}
/**
* Gets cache affinity key. Since in some examples person needs to be
collocated with organization, we create
* custom affinity key to guarantee this collocation.
*
* @return Custom affinity key to guarantee that person is always
collocated with organization.
*/
public AffinityKey<Long> key() {
if (key == null)
key = new AffinityKey<>(id, orgId);
return key;
}
/**
* {@inheritDoc}
*/
@Override public String toString() {
return "Person [id=" + id +
", orgId=" + orgId +
", lastName=" + lastName +
", firstName=" + firstName +
", createdTime=" + createdTime +
", salary=" + salary +
", resume=" + resume + ']';
}
} {code}
DbH2ServerStartup.java:
{code:java}
package org.apache.ignite.examples.util;
import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import org.apache.ignite.IgniteException;
import org.h2.jdbcx.JdbcConnectionPool;
import org.h2.tools.RunScript;
import org.h2.tools.Server;
/**
* Start H2 database TCP server in order to access sample in-memory database
from other processes.
*/
public class DbH2ServerStartup {
/** Create table script. */
private static final String CREATE_PERSON_TABLE =
"create table if not exists PERSON(id bigint AUTO_INCREMENT PRIMARY
KEY, first_name varchar(50), last_name varchar(50), created_time TIMESTAMP
DEFAULT CURRENT_TIMESTAMP);";
/** Sample data script. */
private static final String POPULATE_PERSON_TABLE =
"delete from PERSON;\n" +
"insert into PERSON(id, first_name, last_name) values(1, 'Johannes',
'Kepler');\n" +
"insert into PERSON(id, first_name, last_name) values(2, 'Galileo',
'Galilei');\n" +
"insert into PERSON(id, first_name, last_name) values(3, 'Henry',
'More');\n" +
"insert into PERSON(id, first_name, last_name) values(4, 'Polish',
'Brethren');\n" +
"insert into PERSON(id, first_name, last_name) values(5, 'Robert',
'Boyle');\n" +
"insert into PERSON(id, first_name, last_name) values(6, 'Wilhelm',
'Leibniz');";
/**
* 初始化数据库
*
* @throws SQLException if
*/
public static void populateDatabase() throws SQLException {
// Try to connect to database TCP server.
JdbcConnectionPool dataSrc =
JdbcConnectionPool.create("jdbc:h2:tcp://localhost/mem:ExampleDb", "sa", "");
// Create Person table in database.
RunScript.execute(dataSrc.getConnection(), new
StringReader(CREATE_PERSON_TABLE));
// Populates Person table with sample data in database.
RunScript.execute(dataSrc.getConnection(), new
StringReader(POPULATE_PERSON_TABLE));
}
/**
* Start H2 database TCP server.
*
* @param args Command line arguments, none required.
* @throws IgniteException If start H2 database TCP server failed.
*/
public static void main(String[] args) throws IgniteException {
try {
// Start H2 database TCP server in order to access sample in-memory
database from other processes.
Server.createTcpServer("-tcpDaemon").start();
Server.createWebServer("-webDaemon").start();
populateDatabase();
}
catch (SQLException e) {
throw new IgniteException("Failed to start database TCP server", e);
}
try {
do {
System.out.println("Type 'q' and press 'Enter' to stop H2 TCP
server...");
}
while ('q' != System.in.read());
}
catch (IOException ignored) {
// No-op.
}
}
}
{code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)