diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java new file mode 100644 index 000000000..1e96c4406 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreationEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published after instantiation and setting of all the properties of an entity. This allows to do some + * postprocessing of entities. + * + * @author Jens Schauder + */ +public class AfterCreationEvent extends JdbcEvent{ + + /** + * @param instance the newly instantiated entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterCreationEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java new file mode 100644 index 000000000..982ebbc2a --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * get published after deletion of an entity. The source might contain the Id or the actual entity, depending on the + * {@code delete(...)} method used. + * + * @author Jens Schauder + */ +public class AfterDeleteEvent extends JdbcEvent{ + + /** + * @param instance the deleted entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterDeleteEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java new file mode 100644 index 000000000..aa733f910 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsertEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published after an entity got inserted into the database. + * + * @author Jens Schauder + */ +public class AfterInsertEvent extends AfterSaveEvent { + + /** + * @param instance the newly inserted entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterInsertEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java new file mode 100644 index 000000000..bf25700f6 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * subclasses of this get published after a new instance or a changed instance was saved in the database + * @author Jens Schauder + */ +public class AfterSaveEvent extends JdbcEvent{ + + /** + * @param instance the newly saved entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + AfterSaveEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java new file mode 100644 index 000000000..075fde2be --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdateEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published after an entity was updated in the database. + * + * @author Jens Schauder + */ +public class AfterUpdateEvent extends AfterSaveEvent { + + /** + * @param instance the updated entity. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public AfterUpdateEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java new file mode 100644 index 000000000..1e9a78997 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +import org.springframework.context.ApplicationEvent; + +/** + * gets published when an entity is about to get deleted. {@link ApplicationEvent#getSource()} might contain either the + * entity or the id of the entity, depending on which delete method was used. + * + * @author Jens Schauder + */ +public class BeforeDeleteEvent extends JdbcEvent { + + /** + * @param instance the entity about to get deleted. Might be {@literal NULL} + * @param idProvider a function providing the id, for the instance. Must provide a not {@literal NULL} id, when called with {@link #instance} + * @param type of the entity and the argument of the {@code idProvider} + */ + public BeforeDeleteEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java new file mode 100644 index 000000000..45ecba984 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsertEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published before an entity gets inserted into the database. + * + * When the id-property of the entity must get set manually, an event listener for this event may do so. + * + * @author Jens Schauder + */ +public class BeforeInsertEvent extends BeforeSaveEvent { + + /** + * @param instance the entity about to get inserted. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public BeforeInsertEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java new file mode 100644 index 000000000..626c6f038 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +import org.springframework.context.ApplicationEvent; + +/** + * subclasses of this get published before an entity gets saved to the database. + * + * @author Jens Schauder + */ +public class BeforeSaveEvent extends JdbcEvent { + + /** + * @param instance the entity about to get saved. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + BeforeSaveEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java new file mode 100644 index 000000000..543cbcd78 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdateEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +/** + * gets published before an entity gets updated in the database. + * + * @author Jens Schauder + */ +public class BeforeUpdateEvent extends BeforeSaveEvent { + + /** + * @param instance the entity about to get saved. + * @param idProvider a function providing the id, for the instance. + * @param type of the entity and the argument of the {@code idProvider} + */ + public BeforeUpdateEvent(T instance, Function idProvider) { + super(instance, idProvider); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java new file mode 100644 index 000000000..ad0df42f2 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.event; + +import java.util.function.Function; + +import org.springframework.context.ApplicationEvent; + +/** + * is the common superclass for all events published by JDBC repositories. + * + * It is recommendet not to use the {@link #getSource()} since it may contain the entity if it was available, when the + * event was published, or in case of delete events only the Id. + * + * Use the dedicated methods {@link #getId()} or {@link #getInstance()} instead. Note that the later might be + * {@literal NULL} in the cases mentioned above. + * + * @author Jens Schauder + */ +public class JdbcEvent extends ApplicationEvent { + + private final Object id; + private final Object instance; + + JdbcEvent(T instance, Function idProvider) { + + super(instance == null ? idProvider.apply(instance) : instance); + this.instance = instance; + this.id = idProvider.apply(instance); + } + + /** + * the entity for which this event was publish. Might be {@literal NULL} in cases of delete events where only the id + * was provided to the delete method. + * + * @return instance of the entity triggering this event. + */ + public Object getInstance() { + return instance; + } + + /** + * the id of the entity, triggering this event. Guaranteed not to be {@literal NULL}. + * @return + */ + public Object getId() { + return id; + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java index 4bde019f0..b60435215 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java +++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java @@ -26,13 +26,14 @@ import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.jdbc.core.RowMapper; /** * maps a ResultSet to an entity of type {@code T} * * @author Jens Schauder */ -class EntityRowMapper implements org.springframework.jdbc.core.RowMapper { +class EntityRowMapper implements RowMapper { private final JdbcPersistentEntity entity; diff --git a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java new file mode 100644 index 000000000..b499296c8 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.repository; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.jdbc.mapping.event.AfterCreationEvent; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.jdbc.core.RowMapper; + +/** + * a RowMapper that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an entity. + * + * @author Jens Schauder + */ +public class EventPublishingEntityRowMapper implements RowMapper { + + private final RowMapper delegate; + private final JdbcPersistentEntity entity; + private final ApplicationEventPublisher publisher; + + /** + * + * @param delegate does the actuall mapping. + * @param entity provides functionality to create ids from entities + * @param publisher used for event publishing after the mapping. + */ + EventPublishingEntityRowMapper(RowMapper delegate,JdbcPersistentEntity entity, ApplicationEventPublisher publisher) { + + this.delegate = delegate; + this.entity = entity; + this.publisher = publisher; + } + + @Override + public T mapRow(ResultSet resultSet, int i) throws SQLException { + + T instance = delegate.mapRow(resultSet, i); + + publisher.publishEvent(new AfterCreationEvent(instance, entity::getIdValue)); + + return instance; + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java index 8ec48d2d5..04aaf4672 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java +++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java @@ -20,7 +20,15 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import javax.sql.DataSource; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.data.jdbc.mapping.event.AfterDeleteEvent; +import org.springframework.data.jdbc.mapping.event.AfterInsertEvent; +import org.springframework.data.jdbc.mapping.event.AfterUpdateEvent; +import org.springframework.data.jdbc.mapping.event.BeforeDeleteEvent; +import org.springframework.data.jdbc.mapping.event.BeforeInsertEvent; +import org.springframework.data.jdbc.mapping.event.BeforeUpdateEvent; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation; @@ -28,47 +36,46 @@ import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; +import org.springframework.util.Assert; /** * @author Jens Schauder */ -public class SimpleJdbcRepository implements CrudRepository { +public class SimpleJdbcRepository + implements CrudRepository, ApplicationEventPublisherAware { private final JdbcPersistentEntity entity; - private final JdbcPersistentEntityInformation entityInformation; - private final NamedParameterJdbcOperations template; + private final JdbcPersistentEntityInformation entityInformation; + private final NamedParameterJdbcOperations operations; private final SqlGenerator sql; private final EntityRowMapper entityRowMapper; + private final ApplicationEventPublisher publisher; + + public SimpleJdbcRepository(JdbcPersistentEntity persistentEntity, NamedParameterJdbcOperations jdbcOperations, ApplicationEventPublisher publisher) { - public SimpleJdbcRepository(JdbcPersistentEntity entity, DataSource dataSource) { + Assert.notNull(persistentEntity, "PersistentEntity must not be null."); + Assert.notNull(jdbcOperations, "JdbcOperations must not be null."); + Assert.notNull(publisher, "Publisher must not be null."); - this.entity = entity; - this.entityInformation = new JdbcPersistentEntityInformation(entity); - this.template = new NamedParameterJdbcTemplate(dataSource); + this.entity = persistentEntity; + this.entityInformation = new JdbcPersistentEntityInformation(persistentEntity); + this.operations = jdbcOperations; + this.publisher = publisher; - entityRowMapper = new EntityRowMapper(entity); - sql = new SqlGenerator(entity); + entityRowMapper = new EntityRowMapper(persistentEntity); + sql = new SqlGenerator(persistentEntity); } @Override public S save(S instance) { if (entityInformation.isNew(instance)) { - - KeyHolder holder = new GeneratedKeyHolder(); - - template.update( - sql.getInsert(), - new MapSqlParameterSource(getPropertyMap(instance)), - holder); - - entity.setId(instance, holder.getKey()); + doInsert(instance); } else { - template.update(sql.getUpdate(), getPropertyMap(instance)); + doUpdate(instance); } return instance; @@ -85,7 +92,7 @@ public class SimpleJdbcRepository implements CrudRep @Override public T findOne(ID id) { - return template.queryForObject( + return operations.queryForObject( sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper @@ -95,7 +102,7 @@ public class SimpleJdbcRepository implements CrudRep @Override public boolean exists(ID id) { - return template.queryForObject( + return operations.queryForObject( sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class @@ -104,37 +111,34 @@ public class SimpleJdbcRepository implements CrudRep @Override public Iterable findAll() { - return template.query(sql.getFindAll(), entityRowMapper); + return operations.query(sql.getFindAll(), entityRowMapper); } @Override public Iterable findAll(Iterable ids) { - return template.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper); + return operations.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper); } @Override public long count() { - return template.getJdbcOperations().queryForObject(sql.getCount(), Long.class); + return operations.getJdbcOperations().queryForObject(sql.getCount(), Long.class); } @Override public void delete(ID id) { - template.update(sql.getDeleteById(), new MapSqlParameterSource("id", id)); + doDelete(id, null); } @Override public void delete(T instance) { - template.update( - sql.getDeleteById(), - new MapSqlParameterSource("id", - entity.getIdValue(instance))); + doDelete((ID) entity.getIdValue(instance), instance); } @Override public void delete(Iterable entities) { - template.update( + operations.update( sql.getDeleteByList(), new MapSqlParameterSource("ids", StreamSupport @@ -147,7 +151,12 @@ public class SimpleJdbcRepository implements CrudRep @Override public void deleteAll() { - template.getJdbcOperations().update(sql.getDeleteAll()); + operations.getJdbcOperations().update(sql.getDeleteAll()); + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + } private Map getPropertyMap(final S instance) { @@ -163,4 +172,34 @@ public class SimpleJdbcRepository implements CrudRep return parameters; } + + private void doInsert(S instance) { + publisher.publishEvent(new BeforeInsertEvent(instance, entity::getIdValue)); + + KeyHolder holder = new GeneratedKeyHolder(); + + operations.update( + sql.getInsert(), + new MapSqlParameterSource(getPropertyMap(instance)), + holder); + + entity.setId(instance, holder.getKey()); + + publisher.publishEvent(new AfterInsertEvent(instance, entity::getIdValue)); + } + + private void doDelete(ID id, Object instance) { + + publisher.publishEvent(new BeforeDeleteEvent(instance, o -> id)); + operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", id)); + publisher.publishEvent(new AfterDeleteEvent(instance, o -> id)); + } + + private void doUpdate(S instance) { + publisher.publishEvent(new BeforeUpdateEvent(instance, entity::getIdValue)); + + operations.update(sql.getUpdate(), getPropertyMap(instance)); + + publisher.publishEvent(new AfterUpdateEvent(instance, entity::getIdValue)); + } } diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index d24598250..f2e9b8952 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -16,7 +16,8 @@ package org.springframework.data.jdbc.repository.support; import java.io.Serializable; -import javax.sql.DataSource; + +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.mapping.context.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.repository.SimpleJdbcRepository; @@ -24,27 +25,38 @@ import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** * @author Jens Schauder */ public class JdbcRepositoryFactory extends RepositoryFactorySupport { - private final DataSource dataSource; private final JdbcMappingContext context = new JdbcMappingContext(); + private final ApplicationEventPublisher publisher; + private final NamedParameterJdbcOperations jdbcOperations; + + public JdbcRepositoryFactory( + ApplicationEventPublisher publisher, + NamedParameterJdbcOperations jdbcOperations + ) { - public JdbcRepositoryFactory(DataSource dataSource) { - this.dataSource = dataSource; + this.publisher = publisher; + this.jdbcOperations = jdbcOperations; } @Override public EntityInformation getEntityInformation(Class aClass) { - return new JdbcPersistentEntityInformation((JdbcPersistentEntity) context.getPersistentEntity(aClass)); + return new JdbcPersistentEntityInformation<>((JdbcPersistentEntity) context.getPersistentEntity(aClass)); } @Override protected Object getTargetRepository(RepositoryInformation repositoryInformation) { - return new SimpleJdbcRepository(context.getPersistentEntity(repositoryInformation.getDomainType()), dataSource); + + return new SimpleJdbcRepository( + context.getPersistentEntity(repositoryInformation.getDomainType()), + jdbcOperations, + publisher); } @Override diff --git a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java new file mode 100644 index 000000000..44113c48c --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java @@ -0,0 +1,47 @@ +package org.springframework.data.jdbc.repository; + +import static org.mockito.Mockito.*; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.event.AfterCreationEvent; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.jdbc.core.RowMapper; + +import lombok.Data; + +/** + * @author Jens Schauder + */ +public class EventPublishingEntityRowMapperTest { + + private RowMapper rowMapperDelegate = mock(RowMapper.class); + private JdbcPersistentEntity entity = mock(JdbcPersistentEntity.class); + private ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); + + @Test // DATAJDBC-99 + public void eventGetsPublishedAfterInstantiation() throws SQLException { + + when(entity.getIdValue(any())).thenReturn(1L); + + EventPublishingEntityRowMapper rowMapper = new EventPublishingEntityRowMapper<>( + rowMapperDelegate, + entity, + publisher); + + ResultSet resultSet = mock(ResultSet.class); + rowMapper.mapRow(resultSet, 1); + + verify(publisher).publishEvent(isA(AfterCreationEvent.class)); + } + + @Data + private static class DummyEntity { + + @Id private final Long Id; + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java index 2f2b5396a..f537f6d00 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -17,9 +17,11 @@ package org.springframework.data.jdbc.repository; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; import org.junit.After; import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; @@ -56,7 +58,7 @@ public class JdbcRepositoryIdGenerationIntegrationTests { db.shutdown(); } - @Test + @Test // DATAJDBC-98 public void idWithoutSetterGetsSet() { entity = repository.save(entity); @@ -73,7 +75,7 @@ public class JdbcRepositoryIdGenerationIntegrationTests { reloadedEntity.getName()); } - @Test + @Test // DATAJDBC-98 public void primitiveIdGetsSet() { entity = repository.save(entity); @@ -92,7 +94,11 @@ public class JdbcRepositoryIdGenerationIntegrationTests { private static ReadOnlyIdEntityRepository createRepository(EmbeddedDatabase db) { - return new JdbcRepositoryFactory(db).getRepository(ReadOnlyIdEntityRepository.class); + + return new JdbcRepositoryFactory( + mock(ApplicationEventPublisher.class), + new NamedParameterJdbcTemplate(db) + ).getRepository(ReadOnlyIdEntityRepository.class); } diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 410b92fbd..8a90a9c54 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -18,9 +18,11 @@ package org.springframework.data.jdbc.repository; import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; import org.junit.After; import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; @@ -58,8 +60,7 @@ public class JdbcRepositoryIntegrationTests { db.shutdown(); } - - @Test + @Test // DATAJDBC-95 public void canSaveAnEntity() { entity = repository.save(entity); @@ -74,7 +75,7 @@ public class JdbcRepositoryIntegrationTests { count); } - @Test + @Test // DATAJDBC-95 public void canSaveAndLoadAnEntity() { entity = repository.save(entity); @@ -89,17 +90,22 @@ public class JdbcRepositoryIntegrationTests { reloadedEntity.getName()); } - @Test + @Test // DATAJDBC-97 public void saveMany() { DummyEntity other = createDummyEntity(); repository.save(asList(entity, other)); - assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + assertThat(repository.findAll()) + .extracting(DummyEntity::getIdProp) + .containsExactlyInAnyOrder( + entity.getIdProp(), + other.getIdProp() + ); } - @Test + @Test // DATAJDBC-97 public void existsReturnsTrueIffEntityExists() { entity = repository.save(entity); @@ -108,7 +114,7 @@ public class JdbcRepositoryIntegrationTests { assertFalse(repository.exists(entity.getIdProp() + 1)); } - @Test + @Test // DATAJDBC-97 public void findAllFindsAllEntities() { DummyEntity other = createDummyEntity(); @@ -118,10 +124,11 @@ public class JdbcRepositoryIntegrationTests { Iterable all = repository.findAll(); - assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); + assertThat(all).extracting("idProp") + .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void findAllFindsAllSpecifiedEntities() { DummyEntity two = repository.save(createDummyEntity()); @@ -130,10 +137,11 @@ public class JdbcRepositoryIntegrationTests { Iterable all = repository.findAll(asList(entity.getIdProp(), three.getIdProp())); - assertThat(all).extracting("idProp").containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); + assertThat(all).extracting("idProp") + .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void count() { repository.save(createDummyEntity()); @@ -143,7 +151,7 @@ public class JdbcRepositoryIntegrationTests { assertThat(repository.count()).isEqualTo(3L); } - @Test + @Test // DATAJDBC-97 public void deleteById() { entity = repository.save(entity); @@ -157,7 +165,7 @@ public class JdbcRepositoryIntegrationTests { .containsExactlyInAnyOrder(entity.getIdProp(), three.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void deleteByEntity() { entity = repository.save(entity); @@ -166,11 +174,16 @@ public class JdbcRepositoryIntegrationTests { repository.delete(entity); - assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp(), three.getIdProp()); + assertThat(repository.findAll()) + .extracting(DummyEntity::getIdProp) + .containsExactlyInAnyOrder( + two.getIdProp(), + three.getIdProp() + ); } - @Test + @Test // DATAJDBC-97 public void deleteByList() { repository.save(entity); @@ -182,7 +195,7 @@ public class JdbcRepositoryIntegrationTests { assertThat(repository.findAll()).extracting(DummyEntity::getIdProp).containsExactlyInAnyOrder(two.getIdProp()); } - @Test + @Test // DATAJDBC-97 public void deleteAll() { repository.save(entity); @@ -195,7 +208,7 @@ public class JdbcRepositoryIntegrationTests { } - @Test + @Test // DATAJDBC-98 public void update() { entity = repository.save(entity); @@ -209,7 +222,7 @@ public class JdbcRepositoryIntegrationTests { assertThat(reloaded.getName()).isEqualTo(entity.getName()); } - @Test + @Test // DATAJDBC-98 public void updateMany() { entity = repository.save(entity); @@ -226,7 +239,9 @@ public class JdbcRepositoryIntegrationTests { } private static DummyEntityRepository createRepository(EmbeddedDatabase db) { - return new JdbcRepositoryFactory(db).getRepository(DummyEntityRepository.class); + + return new JdbcRepositoryFactory(mock(ApplicationEventPublisher.class), new NamedParameterJdbcTemplate(db)) + .getRepository(DummyEntityRepository.class); } diff --git a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java new file mode 100644 index 000000000..36c06e17c --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -0,0 +1,116 @@ +package org.springframework.data.jdbc.repository; + +import static java.util.Arrays.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.springframework.util.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.event.AfterDeleteEvent; +import org.springframework.data.jdbc.mapping.event.AfterInsertEvent; +import org.springframework.data.jdbc.mapping.event.AfterUpdateEvent; +import org.springframework.data.jdbc.mapping.event.BeforeDeleteEvent; +import org.springframework.data.jdbc.mapping.event.BeforeInsertEvent; +import org.springframework.data.jdbc.mapping.event.BeforeUpdateEvent; +import org.springframework.data.jdbc.mapping.event.JdbcEvent; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; + +import lombok.Data; + +/** + * @author Jens Schauder + */ +public class SimpleJdbcRepositoryEventsUnitTests { + + private FakePublisher publisher = new FakePublisher(); + + private DummyEntityRepository repository; + + @Before + public void before() { + JdbcRepositoryFactory factory = new JdbcRepositoryFactory(publisher, mock(NamedParameterJdbcOperations.class)); + repository = factory.getRepository(DummyEntityRepository.class); + } + + @Test // DATAJDBC-99 + public void publishesEventsOnSave() { + + DummyEntity entity = new DummyEntity(23L); + + repository.save(entity); + + isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(0)); + isInstanceOf(AfterUpdateEvent.class, publisher.events.get(1)); + } + + @Test // DATAJDBC-99 + public void publishesEventsOnSaveMany() { + + DummyEntity entity1 = new DummyEntity(null); + DummyEntity entity2 = new DummyEntity(23L); + + repository.save(asList(entity1, entity2)); + + isInstanceOf(BeforeInsertEvent.class, publisher.events.get(0)); + isInstanceOf(AfterInsertEvent.class, publisher.events.get(1)); + isInstanceOf(BeforeUpdateEvent.class, publisher.events.get(2)); + isInstanceOf(AfterUpdateEvent.class, publisher.events.get(3)); + } + + + @Test // DATAJDBC-99 + public void publishesEventsOnDelete() { + + DummyEntity entity = new DummyEntity(23L); + + repository.delete(entity); + + isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0)); + isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1)); + + assertEquals(entity, publisher.events.get(0).getInstance()); + assertEquals(entity, publisher.events.get(1).getInstance()); + + assertEquals(23L, publisher.events.get(0).getId()); + assertEquals(23L, publisher.events.get(1).getId()); + } + + + @Test // DATAJDBC-99 + public void publishesEventsOnDeleteById() { + + repository.delete(23L); + + isInstanceOf(BeforeDeleteEvent.class, publisher.events.get(0)); + isInstanceOf(AfterDeleteEvent.class, publisher.events.get(1)); + } + + + @Data + private static class DummyEntity { + + @Id private final Long id; + } + + private interface DummyEntityRepository extends CrudRepository { + + } + + static class FakePublisher implements ApplicationEventPublisher { + + List events = new ArrayList<>(); + + @Override + public void publishEvent(Object o) { + events.add((JdbcEvent) o); + } + } +} \ No newline at end of file