DATAJDBC-130 - Support entities with List valued properties.

This commit is contained in:
Jens Schauder
2018-02-07 12:08:42 +01:00
committed by Greg Turnquist
parent 7ed2ed7d7e
commit f5de195768
13 changed files with 440 additions and 37 deletions

View File

@@ -200,11 +200,11 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
Class<?> actualType = property.getActualType();
String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName(),
property.getKeyColumn());
property.getKeyColumn(), property.isOrdered());
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
return (Iterable<T>) operations.query(findAllByProperty, parameter, property.isQualified() //
return (Iterable<T>) operations.query(findAllByProperty, parameter, property.isMap() //
? getMapEntityRowMapper(property) //
: getEntityRowMapper(actualType));
}

View File

@@ -19,10 +19,8 @@ import lombok.NonNull;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ClassGeneratingEntityInstantiator;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
@@ -45,6 +43,8 @@ import org.springframework.jdbc.core.RowMapper;
*/
public class EntityRowMapper<T> implements RowMapper<T> {
private static final Converter ITERABLE_OF_ENTRY_TO_MAP_CONVERTER = new IterableOfEntryToMapConverter();
private final JdbcPersistentEntity<T> entity;
private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
private final ConversionService conversions;
@@ -79,13 +79,12 @@ public class EntityRowMapper<T> implements RowMapper<T> {
for (JdbcPersistentProperty property : entity) {
if (Set.class.isAssignableFrom(property.getType())) {
if (property.isCollectionLike()) {
propertyAccessor.setProperty(property, accessStrategy.findAllByProperty(id, property));
} else if (Map.class.isAssignableFrom(property.getType())) {
} else if (property.isMap()) {
Iterable<Object> allByProperty = accessStrategy.findAllByProperty(id, property);
IterableOfEntryToMapConverter converter = new IterableOfEntryToMapConverter();
propertyAccessor.setProperty(property, converter.convert(allByProperty));
propertyAccessor.setProperty(property, ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(allByProperty));
} else {
propertyAccessor.setProperty(property, readFrom(resultSet, property, ""));
}

View File

@@ -95,17 +95,22 @@ class SqlGenerator {
* a referencing entity.
*
* @param columnName name of the column of the FK back to the referencing entity.
* @param keyColumn if the property is of type {@link Map} this column contains the map key.
* @param keyColumn if the property is of type {@link Map} this column contains the map key.
* @param ordered whether the SQL statement should include an ORDER BY for the keyColumn. If this is {@literal true}, the keyColumn must not be {@literal null}.
* @return a SQL String.
*/
String getFindAllByProperty(String columnName, String keyColumn) {
String getFindAllByProperty(String columnName, String keyColumn, boolean ordered) {
Assert.isTrue(keyColumn != null || !ordered, "If the SQL statement should be ordered a keyColumn to order by must be provided.");
String baseSelect = (keyColumn != null) //
? createSelectBuilder().column(cb -> cb.tableAlias(entity.getTableName()).column(keyColumn).as(keyColumn))
.build()
.build()
: getFindAll();
return String.format("%s WHERE %s = :%s", baseSelect, columnName, columnName);
String orderBy = ordered ? " ORDER BY " + keyColumn : "";
return String.format("%s WHERE %s = :%s%s", baseSelect, columnName, columnName, orderBy);
}
String getExists() {

View File

@@ -16,12 +16,6 @@
package org.springframework.data.jdbc.core.conversion;
import lombok.RequiredArgsConstructor;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;
import org.springframework.data.jdbc.core.conversion.DbAction.Insert;
import org.springframework.data.jdbc.core.conversion.DbAction.Update;
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
@@ -33,6 +27,13 @@ import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.util.StreamUtils;
import org.springframework.util.ClassUtils;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;
/**
* Converts an entity that is about to be saved into {@link DbAction}s inside a {@link AggregateChange} that need to be
* executed against the database to recreate the appropriate state in the database.
@@ -157,6 +158,10 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
Class<?> type = p.getType();
if (List.class.isAssignableFrom(type)) {
return listPropertyAsStream(p, propertyAccessor);
}
if (Collection.class.isAssignableFrom(type)) {
return collectionPropertyAsStream(p, propertyAccessor);
}
@@ -178,6 +183,21 @@ public class JdbcEntityWriter extends JdbcEntityWriterSupport {
: ((Collection<Object>) property).stream();
}
private Stream<Object> listPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
Object property = propertyAccessor.getProperty(p);
if (property == null) return Stream.empty();
List<Object> listProperty = (List<Object>) property;
HashMap<Integer, Object> map = new HashMap<>();
for (int i = 0; i < listProperty.size(); i++) {
map.put(i, listProperty.get(i));
}
return map.entrySet().stream().map(e -> (Object) e);
}
private Stream<Object> mapPropertyAsStream(JdbcPersistentProperty p, PersistentPropertyAccessor propertyAccessor) {
Object property = propertyAccessor.getProperty(p);

View File

@@ -15,12 +15,6 @@
*/
package org.springframework.data.jdbc.mapping.model;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
@@ -29,6 +23,13 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* Meta data about a property to be used by repository implementations.
*
@@ -114,7 +115,16 @@ public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProper
@Override
public boolean isQualified() {
return isMap();
return isMap() || isListLike();
}
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}
@Override
public boolean isOrdered() {
return isListLike();
}
private Class columnTypeIfEntity(Class type) {

View File

@@ -51,4 +51,9 @@ public interface JdbcPersistentProperty extends PersistentProperty<JdbcPersisten
* Returns if this property is a qualified property, i.e. a property referencing multiple elements that can get picked by a key or an index.
*/
boolean isQualified();
/**
* Returns whether this property is an ordered property.
*/
boolean isOrdered();
}

View File

@@ -54,6 +54,7 @@ import org.springframework.util.Assert;
public class EntityRowMapperUnitTests {
public static final long ID_FOR_ENTITY_REFERENCING_MAP = 42L;
public static final long ID_FOR_ENTITY_REFERENCING_LIST = 4711L;
public static final long ID_FOR_ENTITY_NOT_REFERENCING_MAP = 23L;
@Test // DATAJDBC-113
@@ -116,6 +117,21 @@ public class EntityRowMapperUnitTests {
.containsExactly(ID_FOR_ENTITY_REFERENCING_MAP, "alpha", 2);
}
@Test // DATAJDBC-130
public void listReferenceGetsLoadedWithAdditionalSelect() throws SQLException {
ResultSet rs = mockResultSet(asList("id", "name"), //
ID_FOR_ENTITY_REFERENCING_LIST, "alpha");
rs.next();
OneToMap extracted = createRowMapper(OneToMap.class).mapRow(rs, 1);
assertThat(extracted) //
.isNotNull() //
.extracting(e -> e.id, e -> e.name, e -> e.children.size()) //
.containsExactly(ID_FOR_ENTITY_REFERENCING_LIST, "alpha", 2);
}
private <T> EntityRowMapper<T> createRowMapper(Class<T> type) {
JdbcMappingContext context = new JdbcMappingContext(mock(NamedParameterJdbcOperations.class));
@@ -130,6 +146,11 @@ public class EntityRowMapperUnitTests {
new SimpleEntry("two", new Trivial()) //
))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_MAP), any(JdbcPersistentProperty.class));
doReturn(new HashSet<>(asList( //
new SimpleEntry(1, new Trivial()), //
new SimpleEntry(2, new Trivial()) //
))).when(accessStrategy).findAllByProperty(eq(ID_FOR_ENTITY_REFERENCING_LIST), any(JdbcPersistentProperty.class));
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(new IterableOfEntryToMapConverter());
DefaultConversionService.addDefaultConverters(conversionService);
@@ -261,4 +282,13 @@ public class EntityRowMapperUnitTests {
String name;
Map<String, Trivial> children;
}
@RequiredArgsConstructor
static class OneToList {
@Id
Long id;
String name;
List<Trivial> children;
}
}

View File

@@ -115,7 +115,7 @@ public class SqlGeneratorUnitTests {
public void findAllByProperty() {
// this would get called when DummyEntity is the element type of a Set
String sql = sqlGenerator.getFindAllByProperty("back-ref", null);
String sql = sqlGenerator.getFindAllByProperty("back-ref", null, false);
assertThat(sql).isEqualTo("SELECT DummyEntity.x_id AS x_id, DummyEntity.x_name AS x_name, "
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further "
@@ -127,7 +127,7 @@ public class SqlGeneratorUnitTests {
public void findAllByPropertyWithKey() {
// this would get called when DummyEntity is th element type of a Map
String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column");
String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", false);
assertThat(sql).isEqualTo("SELECT DummyEntity.x_id AS x_id, DummyEntity.x_name AS x_name, "
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, "
@@ -136,6 +136,26 @@ public class SqlGeneratorUnitTests {
+ "WHERE back-ref = :back-ref");
}
@Test (expected = IllegalArgumentException.class) // DATAJDBC-130
public void findAllByPropertyOrderedWithoutKey() {
String sql = sqlGenerator.getFindAllByProperty("back-ref", null, true);
}
@Test // DATAJDBC-131
public void findAllByPropertyWithKeyOrdered() {
// this would get called when DummyEntity is th element type of a Map
String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", true);
assertThat(sql).isEqualTo("SELECT DummyEntity.x_id AS x_id, DummyEntity.x_name AS x_name, "
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, "
+ "DummyEntity.key-column AS key-column "
+ "FROM DummyEntity LEFT OUTER JOIN ReferencedEntity AS ref ON ref.DummyEntity = DummyEntity.x_id "
+ "WHERE back-ref = :back-ref "
+ "ORDER BY key-column"
);
}
@SuppressWarnings("unused")
static class DummyEntity {

View File

@@ -20,8 +20,10 @@ import static org.mockito.Mockito.*;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -167,7 +169,7 @@ public class JdbcEntityWriterUnitTests {
public void newEntityWithEmptyMapResultsInSingleInsert() {
MapContainer entity = new MapContainer(null);
AggregateChange<SingleReferenceEntity> aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity);
AggregateChange<MapContainer> aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity);
converter.write(entity, aggregateChange);
@@ -183,7 +185,7 @@ public class JdbcEntityWriterUnitTests {
entity.elements.put("one", new Element(null));
entity.elements.put("two", new Element(null));
AggregateChange<SingleReferenceEntity> aggregateChange = new AggregateChange(Kind.SAVE, SetContainer.class, entity);
AggregateChange<MapContainer> aggregateChange = new AggregateChange(Kind.SAVE, MapContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions())
@@ -193,15 +195,53 @@ public class JdbcEntityWriterUnitTests {
tuple(Insert.class, Element.class, "one", "elements"), //
tuple(Insert.class, Element.class, "two", "elements") //
).containsSubsequence( // container comes before the elements
tuple(Insert.class, MapContainer.class, null, ""), //
tuple(Insert.class, Element.class, "two", "elements") //
).containsSubsequence( // container comes before the elements
tuple(Insert.class, MapContainer.class, null, ""), //
tuple(Insert.class, Element.class, "one", "elements") //
tuple(Insert.class, MapContainer.class, null, ""), //
tuple(Insert.class, Element.class, "two", "elements") //
).containsSubsequence( // container comes before the elements
tuple(Insert.class, MapContainer.class, null, ""), //
tuple(Insert.class, Element.class, "one", "elements") //
);
}
@Test // DATAJDBC-112
@Test // DATAJDBC-130
public void newEntityWithEmptyListResultsInSingleInsert() {
ListContainer entity = new ListContainer(null);
AggregateChange<ListContainer> aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()).extracting(DbAction::getClass, DbAction::getEntityType, this::extractPath) //
.containsExactly( //
tuple(Insert.class, ListContainer.class, ""));
}
@Test // DATAJDBC-130
public void newEntityWithListResultsInAdditionalInsertPerElement() {
ListContainer entity = new ListContainer(null);
entity.elements.add( new Element(null));
entity.elements.add( new Element(null));
AggregateChange<ListContainer> aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions())
.extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, this::extractPath) //
.containsExactlyInAnyOrder( //
tuple(Insert.class, ListContainer.class, null, ""), //
tuple(Insert.class, Element.class, 0, "elements"), //
tuple(Insert.class, Element.class, 1, "elements") //
).containsSubsequence( // container comes before the elements
tuple(Insert.class, ListContainer.class, null, ""), //
tuple(Insert.class, Element.class, 1, "elements") //
).containsSubsequence( // container comes before the elements
tuple(Insert.class, ListContainer.class, null, ""), //
tuple(Insert.class, Element.class, 0, "elements") //
);
}
@Test // DATAJDBC-131
public void mapTriggersDeletePlusInsert() {
MapContainer entity = new MapContainer(SOME_ENTITY_ID);
@@ -217,7 +257,26 @@ public class JdbcEntityWriterUnitTests {
tuple(Delete.class, Element.class, null, "elements"), //
tuple(Update.class, MapContainer.class, null, ""), //
tuple(Insert.class, Element.class, "one", "elements") //
);
);
}
@Test // DATAJDBC-130
public void listTriggersDeletePlusInsert() {
ListContainer entity = new ListContainer(SOME_ENTITY_ID);
entity.elements.add( new Element(null));
AggregateChange<ListContainer> aggregateChange = new AggregateChange(Kind.SAVE, ListContainer.class, entity);
converter.write(entity, aggregateChange);
assertThat(aggregateChange.getActions()) //
.extracting(DbAction::getClass, DbAction::getEntityType, this::getListKey, this::extractPath) //
.containsExactly( //
tuple(Delete.class, Element.class, null, "elements"), //
tuple(Update.class, ListContainer.class, null, ""), //
tuple(Insert.class, Element.class, 0, "elements") //
);
}
private CascadingReferenceMiddleElement createMiddleElement(Element first, Element second) {
@@ -232,6 +291,10 @@ public class JdbcEntityWriterUnitTests {
return a.getAdditionalValues().get("MapContainer_key");
}
private Object getListKey(DbAction a) {
return a.getAdditionalValues().get("ListContainer_key");
}
private String extractPath(DbAction action) {
return action.getPropertyPath().toDotPath();
}
@@ -273,6 +336,13 @@ public class JdbcEntityWriterUnitTests {
Map<String, Element> elements = new HashMap<>();
}
@RequiredArgsConstructor
private static class ListContainer {
@Id final Long id;
List< Element> elements = new ArrayList<>();
}
@RequiredArgsConstructor
private static class Element {
@Id final Long id;

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2017-2018 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 junit.framework.AssertionFailedError;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
/**
* Very simple use cases for creation and usage of JdbcRepositories for Entities that contain {@link List}s.
*
* @author Jens Schauder
*/
@ContextConfiguration
@Transactional
public class JdbcRepositoryWithListsIntegrationTests {
@Configuration
@Import(TestConfiguration.class)
static class Config {
@Autowired
JdbcRepositoryFactory factory;
@Bean
Class<?> testClass() {
return JdbcRepositoryWithListsIntegrationTests.class;
}
@Bean
DummyEntityRepository dummyEntityRepository() {
return factory.getRepository(DummyEntityRepository.class);
}
}
@ClassRule
public static final SpringClassRule classRule = new SpringClassRule();
@Rule
public SpringMethodRule methodRule = new SpringMethodRule();
@Autowired
NamedParameterJdbcTemplate template;
@Autowired
DummyEntityRepository repository;
@Test // DATAJDBC-130
public void saveAndLoadEmptyList() {
DummyEntity entity = repository.save(createDummyEntity());
assertThat(entity.id).isNotNull();
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
assertThat(reloaded.content) //
.isNotNull() //
.isEmpty();
}
@Test // DATAJDBC-130
public void saveAndLoadNonEmptyList() {
Element element1 = new Element();
Element element2 = new Element();
DummyEntity entity = createDummyEntity();
entity.content.add(element1);
entity.content.add(element2);
entity = repository.save(entity);
assertThat(entity.id).isNotNull();
assertThat(entity.content).allMatch(v -> v.id != null);
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
assertThat(reloaded.content) //
.isNotNull() //
.extracting(e -> e.id) //
.containsExactlyInAnyOrder(element1.id, element2.id);
}
@Test // DATAJDBC-130
public void findAllLoadsList() {
Element element1 = new Element();
Element element2 = new Element();
DummyEntity entity = createDummyEntity();
entity.content.add(element1);
entity.content.add(element2);
entity = repository.save(entity);
assertThat(entity.id).isNotNull();
assertThat(entity.content).allMatch(v -> v.id != null);
Iterable<DummyEntity> reloaded = repository.findAll();
reloaded.forEach(de -> System.out.println("id " + de.id + " content " + de.content.iterator().next().content));
assertThat(reloaded) //
.extracting(e -> e.id, e -> e.content.size()) //
.containsExactly(tuple(entity.id, entity.content.size()));
}
@Test // DATAJDBC-130
public void updateList() {
Element element1 = createElement("one");
Element element2 = createElement("two");
Element element3 = createElement("three");
DummyEntity entity = createDummyEntity();
entity.content.add(element1);
entity.content.add(element2);
entity = repository.save(entity);
entity.content.remove(element1);
element2.content = "two changed";
entity.content.add(element3);
entity = repository.save(entity);
assertThat(entity.id).isNotNull();
assertThat(entity.content).allMatch(v -> v.id != null);
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
// the elements got properly updated and reloaded
assertThat(reloaded.content) //
.isNotNull();
assertThat(reloaded.content) //
.extracting(e -> e.id, e -> e.content) //
.containsExactly( //
tuple(element2.id, "two changed"), //
tuple(element3.id, "three") //
);
Long count = template.queryForObject("SELECT count(1) FROM Element", new HashMap<>(), Long.class);
assertThat(count).isEqualTo(2);
}
@Test // DATAJDBC-130
public void deletingWithList() {
Element element1 = createElement("one");
Element element2 = createElement("two");
DummyEntity entity = createDummyEntity();
entity.content.add(element1);
entity.content.add(element2);
entity = repository.save(entity);
repository.deleteById(entity.id);
assertThat(repository.findById(entity.id)).isEmpty();
Long count = template.queryForObject("SELECT count(1) FROM Element", new HashMap<>(), Long.class);
assertThat(count).isEqualTo(0);
}
private Element createElement(String content) {
Element element = new Element();
element.content = content;
return element;
}
private static DummyEntity createDummyEntity() {
DummyEntity entity = new DummyEntity();
entity.setName("Entity Name");
return entity;
}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
}
@Data
static class DummyEntity {
@Id
private Long id;
String name;
List<Element> content = new ArrayList<>();
}
@RequiredArgsConstructor
static class Element {
@Id
private Long id;
String content;
}
}

View File

@@ -0,0 +1,2 @@
CREATE TABLE dummyentity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, content VARCHAR(100), DummyEntity_key BIGINT, dummyentity BIGINT);

View File

@@ -0,0 +1,2 @@
CREATE TABLE dummyentity ( id BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id BIGINT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(100), DummyEntity_key BIGINT,dummyentity BIGINT);

View File

@@ -0,0 +1,4 @@
DROP TABLE element;
DROP TABLE dummyentity;
CREATE TABLE dummyentity ( id SERIAL PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id SERIAL PRIMARY KEY, content VARCHAR(100),dummyentity_key BIGINT, dummyentity BIGINT);