All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.requery.sql.EntityDataStore Maven / Gradle / Ivy

There is a newer version: 1.6.0
Show newest version
/*
 * 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> 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> 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 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 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