
io.requery.sql.EntityDataStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of requery Show documentation
Show all versions of requery Show documentation
A light but powerful object mapper and SQL generator for Java/Android
/*
* Copyright 2016 requery.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.requery.sql;
import io.requery.BlockingEntityStore;
import io.requery.EntityCache;
import io.requery.PersistenceException;
import io.requery.ReadOnlyException;
import io.requery.RollbackException;
import io.requery.Transaction;
import io.requery.TransactionException;
import io.requery.TransactionIsolation;
import io.requery.TransactionListener;
import io.requery.cache.EmptyEntityCache;
import io.requery.meta.Attribute;
import io.requery.meta.EntityModel;
import io.requery.meta.QueryAttribute;
import io.requery.meta.Type;
import io.requery.proxy.CompositeKey;
import io.requery.proxy.EntityProxy;
import io.requery.query.Deletion;
import io.requery.query.Expression;
import io.requery.query.Insertion;
import io.requery.query.Result;
import io.requery.query.Scalar;
import io.requery.query.Selection;
import io.requery.query.Tuple;
import io.requery.query.Update;
import io.requery.query.element.QueryElement;
import io.requery.query.function.Count;
import io.requery.sql.platform.PlatformDelegate;
import io.requery.util.ClassMap;
import io.requery.util.Objects;
import io.requery.util.function.Supplier;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import static io.requery.query.element.QueryType.DELETE;
import static io.requery.query.element.QueryType.INSERT;
import static io.requery.query.element.QueryType.SELECT;
import static io.requery.query.element.QueryType.UPDATE;
/**
* Implementation of {@link BlockingEntityStore} that persists and makes queryable
* {@link io.requery.Entity} instances through standard JDBC database connections.
*
* @param base class or interface to restrict all entities that are stored to (e.g.
* {@link Object} or {@link java.io.Serializable} for instance.
*
* @author Nikhil Purushe
*/
@ParametersAreNonnullByDefault
public class EntityDataStore implements BlockingEntityStore {
private final EntityModel entityModel;
private final EntityCache entityCache;
private final ConnectionProvider connectionProvider;
private final ClassMap> readers;
private final ClassMap> writers;
private final CompositeEntityListener stateListeners;
private final CompositeStatementListener statementListeners;
private final UpdateOperation updateOperation;
private final SelectCountOperation countOperation;
private final Executor writeExecutor;
private final Supplier transactionProvider;
private final TransactionIsolation defaultIsolation;
private final Set> transactionListenerFactories;
private final int batchUpdateSize;
private final boolean quoteTableNames;
private final boolean quoteColumnNames;
private final AtomicBoolean closed;
private TransactionMode transactionMode;
private PreparedStatementCache statementCache;
private QueryBuilder.Options queryOptions;
private Mapping mapping;
private Platform platform;
private boolean metadataChecked;
private boolean supportsBatchUpdates;
private DataContext context;
/**
* Creates a new {@link EntityDataStore} with the given {@link DataSource} and
* {@link EntityModel}.
*
* @param dataSource to use
* @param model to use
*/
public EntityDataStore(DataSource dataSource, EntityModel model) {
this(dataSource, model, null);
}
/**
* Creates a new {@link EntityDataStore} with the given {@link DataSource},{@link EntityModel}
* and {@link Mapping}.
*
* @param dataSource to use
* @param model to use
* @param mapping to use
*/
public EntityDataStore(DataSource dataSource, EntityModel model, @Nullable Mapping mapping) {
this(new ConfigurationBuilder(dataSource, model)
.setMapping(mapping)
.build());
}
/**
* Creates a new {@link EntityDataStore} with the given configuration.
*
* @param configuration to use
*/
public EntityDataStore(Configuration configuration) {
closed = new AtomicBoolean();
readers = new ClassMap<>();
writers = new ClassMap<>();
entityModel = Objects.requireNotNull(configuration.entityModel());
connectionProvider = Objects.requireNotNull(configuration.connectionProvider());
mapping = configuration.mapping();
platform = configuration.platform();
transactionMode = configuration.transactionMode();
writeExecutor = configuration.writeExecutor();
quoteColumnNames = configuration.quoteColumnNames();
quoteTableNames = configuration.quoteTableNames();
batchUpdateSize = configuration.batchUpdateSize();
defaultIsolation = configuration.transactionIsolation();
transactionListenerFactories = configuration.transactionListenerFactories();
statementListeners = new CompositeStatementListener(configuration.statementListeners());
stateListeners = new CompositeEntityListener<>();
entityCache = configuration.entityCache() == null ?
new EmptyEntityCache() : configuration.entityCache();
int statementCacheSize = configuration.statementCacheSize();
if (statementCacheSize > 0) {
statementCache = new PreparedStatementCache(statementCacheSize);
}
// set default mapping (otherwise deferred to getConnection()
if (platform != null && mapping == null) {
mapping = new GenericMapping(platform);
}
context = new DataContext();
transactionProvider = new TransactionProvider(context);
updateOperation = new UpdateOperation(context);
countOperation = new SelectCountOperation(context);
if (configuration.useDefaultLogging()) {
LoggingListener logListener = new LoggingListener<>();
stateListeners.addPostLoadListener(logListener);
stateListeners.addPostInsertListener(logListener);
stateListeners.addPostDeleteListener(logListener);
stateListeners.addPostUpdateListener(logListener);
stateListeners.addPreInsertListener(logListener);
stateListeners.addPreDeleteListener(logListener);
stateListeners.addPreUpdateListener(logListener);
stateListeners.enableStateListeners(true);
statementListeners.add(logListener);
} else {
// disable the listener since it's used only for logging right now
stateListeners.enableStateListeners(false);
}
}
@Override
public E insert(E entity) {
insert(entity, null);
return entity;
}
@Override
public K insert(E entity, @Nullable Class keyClass) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
EntityProxy proxy = context.proxyOf(entity, true);
synchronized (proxy.syncObject()) {
EntityWriter writer = context.write(proxy.type().classType());
GeneratedKeys key = writer.insert(entity, proxy, keyClass != null);
transaction.commit();
if (key != null && key.size() > 0 && keyClass != null) {
return keyClass.cast(key.get(0));
}
}
}
return null;
}
@Override
public Iterable insert(Iterable entities) {
insert(entities, null);
return entities;
}
@Override
public Iterable insert(Iterable entities, @Nullable Class keyClass) {
Iterator iterator = entities.iterator();
if (iterator.hasNext()) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
E entity = iterator.next();
EntityProxy proxy = context.proxyOf(entity, true);
EntityWriter writer = context.write(proxy.type().classType());
GeneratedKeys keys = writer.batchInsert(entities, keyClass != null);
if (keys != null) {
@SuppressWarnings("unchecked")
Iterable result = (Iterable) keys;
return result;
}
transaction.commit();
}
}
return null;
}
@Override
public E update(E entity) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
EntityProxy proxy = context.proxyOf(entity, true);
synchronized (proxy.syncObject()) {
context.write(proxy.type().classType()).update(entity, proxy);
transaction.commit();
return entity;
}
}
}
@Override
public Iterable update(Iterable entities) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
for (E entity : entities) {
update(entity);
}
transaction.commit();
}
return entities;
}
@Override
public E upsert(E entity) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
EntityProxy proxy = context.proxyOf(entity, true);
synchronized (proxy.syncObject()) {
EntityWriter writer = context.write(proxy.type().classType());
writer.upsert(entity, proxy);
transaction.commit();
return entity;
}
}
}
@Override
public Iterable upsert(Iterable entities) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
for (E entity : entities) {
upsert(entity);
}
transaction.commit();
}
return entities;
}
@Override
public E refresh(E entity) {
EntityProxy proxy = context.proxyOf(entity, false);
synchronized (proxy.syncObject()) {
return context.read(proxy.type().classType()).refresh(entity, proxy);
}
}
@SuppressWarnings("unchecked")
@Override
public E refresh(E entity, Attribute, ?>... attributes) {
EntityProxy proxy = context.proxyOf(entity, false);
synchronized (proxy.syncObject()) {
return context.read(proxy.type().classType())
.refresh(entity, proxy, (Attribute[]) attributes);
}
}
@SuppressWarnings("unchecked")
@Override
public Iterable refresh(Iterable entities, Attribute, ?>... attributes) {
Iterator iterator = entities.iterator();
if (iterator.hasNext()) {
E entity = iterator.next();
EntityProxy proxy = context.proxyOf(entity, false);
EntityReader reader = context.read(proxy.type().classType());
return reader.batchRefresh(entities, (Attribute[])attributes);
}
return entities;
}
@Override
public E refreshAll(E entity) {
EntityProxy proxy = context.proxyOf(entity, false);
synchronized (proxy.syncObject()) {
return context.read(proxy.type().classType()).refreshAll(entity, proxy);
}
}
@Override
public Void delete(E entity) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
EntityProxy proxy = context.proxyOf(entity, true);
synchronized (proxy.syncObject()) {
context.write(proxy.type().classType()).delete(entity, proxy);
transaction.commit();
}
}
return null;
}
@Override
public Void delete(Iterable entities) {
Iterator iterator = entities.iterator();
if (iterator.hasNext()) {
try (TransactionScope transaction = new TransactionScope(transactionProvider)) {
E entity = iterator.next();
EntityProxy proxy = context.proxyOf(entity, false);
EntityWriter writer = context.write(proxy.type().classType());
writer.batchDelete(entities);
transaction.commit();
}
}
return null;
}
@Override
public E findByKey(Class type, K key) {
Type entityType = entityModel.typeOf(type);
if (entityType.isCacheable() && entityCache != null) {
E entity = entityCache.get(type, key);
if (entity != null) {
return entity;
}
}
Set> keys = entityType.keyAttributes();
if (keys.isEmpty()) {
throw new MissingKeyException();
}
Selection> selection = select(type);
if (keys.size() == 1) {
QueryAttribute attribute = Attributes.query(keys.iterator().next());
selection.where(attribute.equal(key));
} else {
if (key instanceof CompositeKey) {
CompositeKey compositeKey = (CompositeKey) key;
for (Attribute attribute : keys) {
QueryAttribute keyAttribute = Attributes.query(attribute);
Object value = compositeKey.get(keyAttribute);
selection.where(keyAttribute.equal(value));
}
} else {
throw new IllegalArgumentException("CompositeKey required");
}
}
return selection.get().first();
}
@Override
public Transaction transaction() {
checkClosed();
return transactionProvider.get();
}
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
entityCache.clear();
if (statementCache != null) {
statementCache.close();
}
}
}
@Override
public Selection> select(Expression>... expressions) {
TupleResultReader reader = new TupleResultReader(context);
SelectOperation select = new SelectOperation<>(context, reader);
return new QueryElement<>(SELECT, entityModel, select).select(expressions);
}
@Override
public Selection> select(Set extends Expression>> expressions) {
TupleResultReader reader = new TupleResultReader(context);
SelectOperation select = new SelectOperation<>(context, reader);
return new QueryElement<>(SELECT, entityModel, select).select(expressions);
}
@Override
public Update> update() {
checkClosed();
return new QueryElement<>(UPDATE, entityModel, updateOperation);
}
@Override
public Deletion> delete() {
checkClosed();
return new QueryElement<>(DELETE, entityModel, updateOperation);
}
@Override
public Selection> select(Class type,
QueryAttribute, ?>... attributes) {
checkClosed();
EntityReader reader = context.read(type);
Set> selection;
ResultReader resultReader;
if (attributes.length == 0) {
selection = reader.defaultSelection();
resultReader = reader.newResultReader(reader.defaultSelectionAttributes());
} else {
selection = new LinkedHashSet<>(Arrays.>asList(attributes));
resultReader = reader.newResultReader(attributes);
}
SelectOperation select = new SelectOperation<>(context, resultReader);
QueryElement> query = new QueryElement<>(SELECT, entityModel, select);
return query.select(selection).from(type);
}
@Override
public Selection> select(
Class type, Set extends QueryAttribute> attributes) {
QueryAttribute, ?>[] array = attributes.toArray(new QueryAttribute[attributes.size()]);
return select(type, array);
}
@Override
public Insertion> insert(Class type) {
checkClosed();
Type entityType = context.model().typeOf(type);
Set> keySelection = new LinkedHashSet<>();
for (Attribute attribute : entityType.keyAttributes()) {
keySelection.add((Expression>) attribute);
}
InsertReturningOperation operation = new InsertReturningOperation(context, keySelection);
return new QueryElement<>(INSERT, entityModel, operation).from(type);
}
@Override
public Update> update(Class type) {
checkClosed();
return new QueryElement<>(UPDATE, entityModel, updateOperation).from(type);
}
@Override
public Deletion> delete(Class type) {
checkClosed();
return new QueryElement<>(DELETE, entityModel, updateOperation).from(type);
}
@Override
public Selection> count(Class type) {
checkClosed();
Objects.requireNotNull(type);
return new QueryElement<>(SELECT, entityModel, countOperation)
.select(Count.count(type)).from(type);
}
@Override
public Selection> count(QueryAttribute, ?>... attributes) {
checkClosed();
return new QueryElement<>(SELECT, entityModel, countOperation)
.select(Count.count(attributes));
}
@Override
public Result raw(final String query, final Object... parameters) {
checkClosed();
return new RawTupleQuery(context, query, parameters).get();
}
@Override
public Result raw(Class type, String query, Object... parameters) {
checkClosed();
return new RawEntityQuery<>(context, type, query, parameters).get();
}
@Override
public V runInTransaction(Callable callable, @Nullable TransactionIsolation isolation) {
Objects.requireNotNull(callable);
checkClosed();
Transaction transaction = transactionProvider.get();
if (transaction == null) {
throw new TransactionException("no transaction");
}
try {
transaction.begin(isolation);
V result = callable.call();
transaction.commit();
return result;
} catch (Exception e) {
throw new RollbackException(e);
}
}
@Override
public V runInTransaction(Callable callable) {
return runInTransaction(callable, null);
}
@Override
public BlockingEntityStore toBlocking() {
return this;
}
protected synchronized void checkConnectionMetadata() {
// only done once metadata assumed to be the same for every connection
if (!metadataChecked) {
try (Connection connection = context.getConnection()) {
DatabaseMetaData metadata = connection.getMetaData();
if (!metadata.supportsTransactions()) {
transactionMode = TransactionMode.NONE;
}
supportsBatchUpdates = metadata.supportsBatchUpdates();
String quoteIdentifier = metadata.getIdentifierQuoteString();
queryOptions = new QueryBuilder.Options(quoteIdentifier, true,
quoteTableNames, quoteColumnNames);
metadataChecked = true;
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
}
protected void checkClosed() {
if (closed.get()) {
throw new PersistenceException("closed");
}
}
protected EntityContext context() {
return context;
}
private class DataContext implements EntityContext, ConnectionProvider {
@Override
public EntityProxy proxyOf(E entity, boolean forUpdate) {
checkClosed();
@SuppressWarnings("unchecked")
Type type = (Type) entityModel.typeOf(entity.getClass());
EntityProxy proxy = type.proxyProvider().apply(entity);
if (forUpdate && type.isReadOnly()) {
throw new ReadOnlyException();
}
if (forUpdate) {
EntityProxyTransaction transaction = transactionProvider.get();
if (transaction != null && transaction.active()) {
transaction.addToTransaction(proxy);
}
}
return proxy;
}
@Override
public synchronized Connection getConnection() throws SQLException {
Connection connection = null;
Transaction transaction = transactionProvider.get();
// if the transaction holds a connection use that
if (transaction != null && transaction.active()) {
if (transaction instanceof ConnectionProvider) {
ConnectionProvider connectionProvider = (ConnectionProvider) transaction;
connection = connectionProvider.getConnection();
}
}
if (connection == null) {
connection = connectionProvider.getConnection();
if (statementCache != null) {
connection = new StatementCachingConnection(statementCache, connection);
}
}
// lazily create things that depend on a connection
if (platform == null) {
platform = new PlatformDelegate(connection);
}
if (mapping == null) {
mapping = new GenericMapping(platform);
}
return connection;
}
@Override
public synchronized EntityReader read(Class extends E> type) {
@SuppressWarnings("unchecked")
EntityReader reader = (EntityReader) readers.get(type);
if (reader == null) {
checkConnectionMetadata();
reader = new EntityReader<>(entityModel.typeOf(type), this, EntityDataStore.this);
readers.put(type, reader);
}
return reader;
}
@Override
public synchronized EntityWriter write(Class extends E> type) {
@SuppressWarnings("unchecked")
EntityWriter writer = (EntityWriter) writers.get(type);
if (writer == null) {
checkConnectionMetadata();
writer = new EntityWriter<>(entityModel.typeOf(type), this, EntityDataStore.this);
writers.put(type, writer);
}
return writer;
}
@Override
public CompositeEntityListener stateListener() {
return stateListeners;
}
@Override
public ConnectionProvider connectionProvider() {
return this;
}
@Override
public boolean supportsBatchUpdates() {
checkConnectionMetadata();
return supportsBatchUpdates && batchUpdateSize() > 0;
}
@Override
public int batchUpdateSize() {
return batchUpdateSize;
}
@Override
public QueryBuilder.Options queryBuilderOptions() {
checkConnectionMetadata();
return queryOptions;
}
@Override
public Mapping mapping() {
return mapping;
}
@Override
public EntityModel model() {
return entityModel;
}
@Override
public EntityCache cache() {
return entityCache;
}
@Override
public Platform platform() {
return platform;
}
@Override
public StatementListener statementListener() {
return statementListeners;
}
@Override
public Set> transactionListenerFactories() {
return transactionListenerFactories;
}
@Override
public TransactionMode transactionMode() {
checkConnectionMetadata();
return transactionMode;
}
@Override
public TransactionIsolation transactionIsolation() {
return defaultIsolation;
}
@Override
public Executor writeExecutor() {
return writeExecutor;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy