/** * 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; } }