/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.jooq.impl.DSL.noCondition;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.Tools.EMPTY_RECORD;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Optional;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.DAO;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.RecordContext;
import org.jooq.RecordListenerProvider;
import org.jooq.RecordMapper;
import org.jooq.SQLDialect;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.UpdatableRecord;
import org.jooq.conf.Settings;
A common base implementation for generated DAO
.
Unlike many other elements in the jOOQ API, DAO
may be used in
the context of Spring, CDI, or EJB lifecycle management. This means that no
methods in the DAO
type hierarchy must be made final. See also
https://github.com/jOOQ/
jOOQ/issues/4696 for more details.
Author: Lukas Eder
/**
* A common base implementation for generated {@link DAO}.
* <p>
* Unlike many other elements in the jOOQ API, <code>DAO</code> may be used in
* the context of Spring, CDI, or EJB lifecycle management. This means that no
* methods in the <code>DAO</code> type hierarchy must be made final. See also
* <a href="https://github.com/jOOQ/jOOQ/issues/4696">https://github.com/jOOQ/
* jOOQ/issues/4696</a> for more details.
*
* @author Lukas Eder
*/
public abstract class DAOImpl<R extends UpdatableRecord<R>, P, T> implements DAO<R, P, T> {
private final Table<R> table;
private final Class<P> type;
private RecordMapper<R, P> mapper;
private Configuration configuration;
// -------------------------------------------------------------------------
// XXX: Constructors and initialisation
// -------------------------------------------------------------------------
protected DAOImpl(Table<R> table, Class<P> type) {
this(table, type, null);
}
protected DAOImpl(Table<R> table, Class<P> type, Configuration configuration) {
this.table = table;
this.type = type;
setConfiguration(configuration);
}
Inject a configuration.
This method is maintained to be able to configure a DAO
using Spring. It is not exposed in the public API.
/**
* Inject a configuration.
* <p>
* This method is maintained to be able to configure a <code>DAO</code>
* using Spring. It is not exposed in the public API.
*/
public /* non-final */ void setConfiguration(Configuration configuration) {
this.configuration = Tools.configuration(configuration);
this.mapper = this.configuration.recordMapperProvider().provide(table.recordType(), type);
}
public final DSLContext ctx() {
return configuration().dsl();
}
@Override
public /* non-final */ Configuration configuration() {
return configuration;
}
@Override
public /* non-final */ Settings settings() {
return Tools.settings(configuration());
}
@Override
public /* non-final */ SQLDialect dialect() {
return Tools.configuration(configuration()).dialect();
}
@Override
public /* non-final */ SQLDialect family() {
return dialect().family();
}
{@inheritDoc}
Subclasses may override this method to provide custom implementations.
/**
* {@inheritDoc}
* <p>
* Subclasses may override this method to provide custom implementations.
*/
@Override
public /* non-final */ RecordMapper<R, P> mapper() {
return mapper;
}
// -------------------------------------------------------------------------
// XXX: DAO API
// -------------------------------------------------------------------------
@Override
public /* non-final */ void insert(P object) {
insert(singletonList(object));
}
@SuppressWarnings("unchecked")
@Override
public /* non-final */ void insert(P... objects) {
insert(asList(objects));
}
@Override
public /* non-final */ void insert(Collection<P> objects) {
// Execute a batch INSERT
if (objects.size() > 1)
// [#2536] [#3327] We cannot batch INSERT RETURNING calls yet
if (!FALSE.equals(settings().isReturnRecordToPojo()))
for (R record : records(objects, false))
record.insert();
else
ctx().batchInsert(records(objects, false)).execute();
// Execute a regular INSERT
else if (objects.size() == 1)
records(objects, false).get(0).insert();
}
@Override
public /* non-final */ void update(P object) {
update(singletonList(object));
}
@SuppressWarnings("unchecked")
@Override
public /* non-final */ void update(P... objects) {
update(asList(objects));
}
@Override
public /* non-final */ void update(Collection<P> objects) {
// Execute a batch UPDATE
if (objects.size() > 1)
// [#2536] [#3327] We cannot batch UPDATE RETURNING calls yet
if (!FALSE.equals(settings().isReturnRecordToPojo()) &&
TRUE.equals(settings().isReturnAllOnUpdatableRecord()))
for (R record : records(objects, true))
record.update();
else
ctx().batchUpdate(records(objects, true)).execute();
// Execute a regular UPDATE
else if (objects.size() == 1)
records(objects, true).get(0).update();
}
@Override
public /* non-final */ void merge(P object) {
merge(singletonList(object));
}
@SuppressWarnings("unchecked")
@Override
public /* non-final */ void merge(P... objects) {
merge(asList(objects));
}
@Override
public /* non-final */ void merge(Collection<P> objects) {
// Execute a batch MERGE
if (objects.size() > 1)
// [#2536] [#3327] We cannot batch MERGE RETURNING calls yet
if (!FALSE.equals(settings().isReturnRecordToPojo()) &&
TRUE.equals(settings().isReturnAllOnUpdatableRecord()))
for (R record : records(objects, false))
record.merge();
else
ctx().batchMerge(records(objects, false)).execute();
// Execute a regular MERGE
else if (objects.size() == 1)
records(objects, false).get(0).merge();
}
@Override
public /* non-final */ void delete(P object) {
delete(singletonList(object));
}
@SuppressWarnings("unchecked")
@Override
public /* non-final */ void delete(P... objects) {
delete(asList(objects));
}
@Override
public /* non-final */ void delete(Collection<P> objects) {
// Execute a batch DELETE
if (objects.size() > 1)
// [#2536] [#3327] We cannot batch DELETE RETURNING calls yet
if (!FALSE.equals(settings().isReturnRecordToPojo()) &&
TRUE.equals(settings().isReturnAllOnUpdatableRecord()))
for (R record : records(objects, true))
record.delete();
else
ctx().batchDelete(records(objects, true)).execute();
// Execute a regular DELETE
else if (objects.size() == 1)
records(objects, true).get(0).delete();
}
@SuppressWarnings("unchecked")
@Override
public /* non-final */ void deleteById(T... ids) {
deleteById(asList(ids));
}
@Override
public /* non-final */ void deleteById(Collection<T> ids) {
Field<?>[] pk = pk();
if (pk != null)
ctx().delete(table).where(equal(pk, ids)).execute();
}
@Override
public /* non-final */ boolean exists(P object) {
return existsById(getId(object));
}
@Override
public /* non-final */ boolean existsById(T id) {
Field<?>[] pk = pk();
if (pk != null)
return ctx()
.selectCount()
.from(table)
.where(equal(pk, id))
.fetchOne(0, Integer.class) > 0;
else
return false;
}
@Override
public /* non-final */ long count() {
return ctx()
.selectCount()
.from(table)
.fetchOne(0, Long.class);
}
@Override
public /* non-final */ List<P> findAll() {
return ctx()
.selectFrom(table)
.fetch(mapper());
}
@Override
public /* non-final */ P findById(T id) {
Field<?>[] pk = pk();
if (pk != null)
return ctx().selectFrom(table)
.where(equal(pk, id))
.fetchOne(mapper());
return null;
}
@Override
public /* non-final */ <Z> List<P> fetchRange(Field<Z> field, Z lowerInclusive, Z upperInclusive) {
return ctx()
.selectFrom(table)
.where(
lowerInclusive == null
? upperInclusive == null
? noCondition()
: field.le(upperInclusive)
: upperInclusive == null
? field.ge(lowerInclusive)
: field.between(lowerInclusive, upperInclusive))
.fetch(mapper());
}
@SuppressWarnings("unchecked")
@Override
public /* non-final */ <Z> List<P> fetch(Field<Z> field, Z... values) {
return ctx()
.selectFrom(table)
.where(field.in(values))
.fetch(mapper());
}
@Override
public /* non-final */ <Z> P fetchOne(Field<Z> field, Z value) {
return ctx()
.selectFrom(table)
.where(field.equal(value))
.fetchOne(mapper());
}
@Override
public /* non-final */ <Z> Optional<P> fetchOptional(Field<Z> field, Z value) {
return Optional.ofNullable(fetchOne(field, value));
}
@Override
public /* non-final */ Table<R> getTable() {
return table;
}
@Override
public /* non-final */ Class<P> getType() {
return type;
}
// ------------------------------------------------------------------------
// XXX: Template methods for generated subclasses
// ------------------------------------------------------------------------
@SuppressWarnings("unchecked")
protected /* non-final */ T compositeKeyRecord(Object... values) {
UniqueKey<R> key = table.getPrimaryKey();
if (key == null)
return null;
TableField<R, Object>[] fields = (TableField<R, Object>[]) key.getFieldsArray();
Record result = configuration().dsl().newRecord(fields);
for (int i = 0; i < values.length; i++)
result.set(fields[i], fields[i].getDataType().convert(values[i]));
return (T) result;
}
// ------------------------------------------------------------------------
// XXX: Private utility methods
// ------------------------------------------------------------------------
@SuppressWarnings("unchecked")
private /* non-final */ Condition equal(Field<?>[] pk, T id) {
if (pk.length == 1) {
return ((Field<Object>) pk[0]).equal(pk[0].getDataType().convert(id));
}
// [#2573] Composite key T types are of type Record[N]
else {
return row(pk).equal((Record) id);
}
}
@SuppressWarnings("unchecked")
private /* non-final */ Condition equal(Field<?>[] pk, Collection<T> ids) {
if (pk.length == 1) {
if (ids.size() == 1) {
return equal(pk, ids.iterator().next());
}
else {
return ((Field<Object>) pk[0]).in(pk[0].getDataType().convert(ids));
}
}
// [#2573] Composite key T types are of type Record[N]
else {
return row(pk).in(ids.toArray(EMPTY_RECORD));
}
}
private /* non-final */ Field<?>[] pk() {
UniqueKey<?> key = table.getPrimaryKey();
return key == null ? null : key.getFieldsArray();
}
private /* non-final */ List<R> records(Collection<P> objects, boolean forUpdate) {
List<R> result = new ArrayList<>(objects.size());
Field<?>[] pk = pk();
DSLContext ctx;
// [#7731] Create a Record -> POJO mapping to allow for reusing the below
// derived Configuration for improved reflection caching.
IdentityHashMap<R, Object> mapping = null;
// [#2536] Upon store(), insert(), update(), delete(), returned values in the record
// are copied back to the relevant POJO using the RecordListener SPI
if (!FALSE.equals(settings().isReturnRecordToPojo())) {
mapping = new IdentityHashMap<>();
ctx = configuration().derive(providers(configuration().recordListenerProviders(), mapping)).dsl();
}
else
ctx = ctx();
for (P object : objects) {
R record = ctx.newRecord(table, object);
if (mapping != null)
mapping.put(record, object);
if (forUpdate && pk != null)
for (Field<?> field : pk)
record.changed(field, false);
Tools.resetChangedOnNotNull(record);
result.add(record);
}
return result;
}
private /* non-final */ RecordListenerProvider[] providers(final RecordListenerProvider[] providers, final IdentityHashMap<R, Object> mapping) {
RecordListenerProvider[] result = Arrays.copyOf(providers, providers.length + 1);
result[providers.length] = new DefaultRecordListenerProvider(new DefaultRecordListener() {
private final void end(RecordContext ctx) {
Record record = ctx.record();
// TODO: [#2536] Use mapper()
if (record != null)
record.into(mapping.get(record));
}
@Override
public final void storeEnd(RecordContext ctx) {
end(ctx);
}
@Override
public final void insertEnd(RecordContext ctx) {
end(ctx);
}
@Override
public final void updateEnd(RecordContext ctx) {
end(ctx);
}
@Override
public final void deleteEnd(RecordContext ctx) {
end(ctx);
}
});
return result;
}
}