co.jirm.orm.dao.JirmDao.java Source code

Java tutorial

Introduction

Here is the source code for co.jirm.orm.dao.JirmDao.java

Source

/**
 * Copyright (C) 2012 the original author or authors.
 *
 * 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 co.jirm.orm.dao;

import static co.jirm.core.util.JirmPrecondition.check;
import static com.google.common.collect.Iterators.partition;
import static com.google.common.collect.Iterators.peekingIterator;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import co.jirm.core.builder.QueryForNumber;
import co.jirm.core.builder.TypedQueryFor;
import co.jirm.core.execute.SqlExecutor;
import co.jirm.core.util.ObjectMapUtils;
import co.jirm.core.util.ObjectMapUtils.NestedKeyValue;
import co.jirm.mapper.SqlObjectConfig;
import co.jirm.mapper.copy.CopyBuilder;
import co.jirm.mapper.definition.SqlObjectDefinition;
import co.jirm.mapper.definition.SqlParameterDefinition;
import co.jirm.orm.OrmConfig;
import co.jirm.orm.builder.delete.DeleteBuilderFactory;
import co.jirm.orm.builder.delete.DeleteRootClauseBuilder;
import co.jirm.orm.builder.select.SelectBuilderFactory;
import co.jirm.orm.builder.select.SelectRootClauseBuilder;
import co.jirm.orm.builder.update.UpdateBuilderFactory;
import co.jirm.orm.builder.update.UpdateObjectBuilder;
import co.jirm.orm.builder.update.UpdateRootClauseBuilder;
import co.jirm.orm.writer.SqlWriterStrategy;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.PeekingIterator;

public final class JirmDao<T> {

    private final SqlExecutor sqlExecutor;
    private final SqlObjectConfig config;
    private final SqlObjectDefinition<T> definition;
    private final SelectBuilderFactory<T> selectBuilderFactory;
    private final UpdateBuilderFactory<T> updateBuilderFactory;
    private final DeleteBuilderFactory<T> deleteBuilderFactory;
    private final SqlWriterStrategy writerStrategy;

    private JirmDao(SqlExecutor sqlExecutor, SqlObjectConfig config, SqlObjectDefinition<T> definition,
            SqlWriterStrategy writerStrategy, SelectBuilderFactory<T> selectBuilderFactory,
            UpdateBuilderFactory<T> updateBuilderFactory, DeleteBuilderFactory<T> deleteBuilderFactory) {
        super();
        this.sqlExecutor = sqlExecutor;
        this.config = config;
        this.definition = definition;
        this.writerStrategy = writerStrategy;
        this.selectBuilderFactory = selectBuilderFactory;
        this.updateBuilderFactory = updateBuilderFactory;
        this.deleteBuilderFactory = deleteBuilderFactory;
    }

    public static <T> JirmDao<T> newInstance(Class<T> type, OrmConfig config) {
        SqlObjectDefinition<T> definition = config.getSqlObjectConfig().resolveObjectDefinition(type);
        SelectBuilderFactory<T> selectBuilderFactory = SelectBuilderFactory.newInstance(definition, config);
        UpdateBuilderFactory<T> updateBuilderFactory = UpdateBuilderFactory.newInstance(definition, config);
        DeleteBuilderFactory<T> deleteBuilderFactory = DeleteBuilderFactory.newInstance(definition, config);

        return new JirmDao<T>(config.getSqlExecutor(), config.getSqlObjectConfig(), definition,
                config.getSqlWriterStrategy(), selectBuilderFactory, updateBuilderFactory, deleteBuilderFactory);
    }

    private LinkedHashMap<String, Object> toLinkedHashMap(T t, boolean bulkInsert) {
        LinkedHashMap<String, Object> m = config.getObjectMapper().convertObjectToSqlMap(t);
        /*
         * Replace the complex objects with there ids.
         */
        for (SqlParameterDefinition pd : definition.getManyToOneParameters().values()) {
            if (pd.getObjectDefinition().isPresent()
                    && pd.getObjectDefinition().get().getObjectDefintion().idParameter().isPresent()) {
                SqlParameterDefinition idDef = pd.getObjectDefinition().get().getObjectDefintion().idParameter()
                        .get();
                NestedKeyValue<Object> nkv = ObjectMapUtils.getNestedKeyValue(m, pd.getParameterName(),
                        idDef.getParameterName());
                if (nkv.isPresent()) {
                    /*
                     * TODO: We only set it if the object is actually present. ie do you really want to set null?
                     */
                    m.put(pd.getParameterName(), idDef.convertToSql(nkv.object));
                } else if (bulkInsert) {
                    //TODO default annotation perhaps here?
                    //http://stackoverflow.com/questions/197045/setting-default-values-for-columns-in-jpa
                    m.put(pd.getParameterName(), null);
                }
            }
        }
        for (Entry<String, Object> e : m.entrySet()) {
            Optional<SqlParameterDefinition> d = definition.resolveParameter(e.getKey());
            if (d.isPresent()) {
                e.setValue(d.get().convertToSql(e.getValue()));
            }
        }
        if (bulkInsert) {
            LinkedHashMap<String, Object> copy = new LinkedHashMap<String, Object>(
                    definition.getIdParameters().size());
            /*
             * Order and the number of parameters is really important for bulk insert.
             */
            for (SqlParameterDefinition pd : definition.getParameters().values()) {
                check.state(m.containsKey(pd.getParameterName()), "Missing parameter for bulk insert: {}",
                        pd.getParameterName());
                Object o = m.get(pd.getParameterName());
                copy.put(pd.getParameterName(), o);
            }
            m = copy;
        }
        return m;

    }

    public CopyBuilder<T> copyBuilder() {
        return CopyBuilder.newInstance(definition.getObjectType(), config.getObjectMapper());
    }

    protected SqlParameterDefinition idParameter() {
        check.state(definition.idParameter().isPresent(), "No id parameter for : {}", definition.getObjectType());
        return this.definition.idParameter().get();
    }

    public SelectRootClauseBuilder<? extends TypedQueryFor<T>> select() {
        return selectBuilderFactory.select();
    }

    public SelectRootClauseBuilder<? extends QueryForNumber> count() {
        return selectBuilderFactory.count();
    }

    public UpdateRootClauseBuilder<Integer> update() {
        return updateBuilderFactory.update();
    }

    public DeleteRootClauseBuilder<Integer> delete() {
        return deleteBuilderFactory.delete();
    }

    public Optional<T> findOptionalById(Object id) {
        return select().where().property(idParameter().getParameterName()).eq(id).query().forOptional();
    }

    public T findById(Object id) {
        return select().where().property(idParameter().getParameterName()).eq(id).query().forObject();
    }

    public void insert(T t) {
        LinkedHashMap<String, Object> m = toLinkedHashMap(t, false);
        Iterator<Entry<String, Object>> it = m.entrySet().iterator();
        /*
         * Remove the null values that are to be generated.
         */
        while (it.hasNext()) {
            Entry<String, Object> e = it.next();
            Optional<SqlParameterDefinition> p = definition.resolveParameter(e.getKey());
            if (p.isPresent() && p.get().isGenerated() && e.getValue() == null) {
                it.remove();
            } else if (p.isPresent() && p.get().isVersion() && e.getValue() == null) {
                e.setValue(0);
            }
        }
        insert(m);
    }

    public int deleteById(Object id) {
        return deleteBuilderFactory.delete().where().property(idParameter().getParameterName()).eq(id).execute();
    }

    public UpdateObjectBuilder<T> update(T t) {
        LinkedHashMap<String, Object> m = toLinkedHashMap(t, false);
        return updateBuilderFactory.update(m);
    }

    public T reload(T t) {
        LinkedHashMap<String, Object> m = toLinkedHashMap(t, false);
        Optional<SqlParameterDefinition> id = definition.idParameter();
        check.state(id.isPresent(), "No id definition");
        Optional<Object> o = id.get().valueFrom(m);
        return findById(o.get());
    }

    public void insert(Map<String, Object> values) {
        StringBuilder qb = new StringBuilder();
        writerStrategy.insertStatement(qb, definition, values);
        sqlExecutor.update(qb.toString(), writerStrategy.fillValues(definition, values).toArray());
    }

    public void insert(Iterator<T> values, int batchSize) {
        Iterator<Map<String, Object>> t = Iterators.transform(values, new Function<T, Map<String, Object>>() {
            @Override
            public Map<String, Object> apply(T input) {
                return toLinkedHashMap(input, true);
            }
        });
        insertMaps(t, batchSize);
    }

    public void insertMaps(Iterator<Map<String, Object>> values, int batchSize) {
        if (!values.hasNext())
            return;
        PeekingIterator<Map<String, Object>> vs = peekingIterator(values);
        Map<String, Object> first = vs.peek();
        final String sql = writerStrategy.insertStatement(new StringBuilder(), definition, first).toString();
        ImmutableList<String> keys = ImmutableList.copyOf(vs.peek().keySet());
        Iterator<List<Map<String, Object>>> it = partition(vs, batchSize);

        while (it.hasNext()) {
            List<Map<String, Object>> batch = it.next();
            final List<Object[]> batchValues = Lists.newArrayListWithExpectedSize(batch.size());
            for (Map<String, Object> b : batch) {
                ImmutableList<String> actualKeys = ImmutableList.copyOf(b.keySet());
                check.state(actualKeys.equals(keys), "Keys don't match up to {} for {}", keys, actualKeys);
                batchValues.add(writerStrategy.fillValues(definition, b).toArray());
            }
            /*
             * TODO this will keep making a prepared statementS.
             * Hopefully the JDBC driver has some caching for this.
             */
            sqlExecutor.batchUpdate(sql, batchValues);
        }

    }

    public SelectBuilderFactory<T> getSelectBuilderFactory() {
        return selectBuilderFactory;
    }

    public UpdateBuilderFactory<T> getUpdateBuilderFactory() {
        return updateBuilderFactory;
    }
}