Support for ElementCollection and transient collections as entity attributes
I'd love to see ElementCollection (List<String> for me). I don't need the transient use case, but it looks like it might come for free with data model support.
@ElementCollection with @CollectionTable for List<String> is a very common case imho - when some domain object has a collection of associated email addresses / telephone numbers / role names.
Even own Jmix modules have many cases for using such an option, e.g. io.jmix.email.entity.SendingMessage#address, or "index" fields Report#rolesIdx, Report#screensIdx, Report#inputEntityTypesIdx.
Historically they store list of attributes in one String column with delimiter.
Implementation
This request is addressed in PR #4930
Model
Supported
List and Set of the following types: String, BigDecimal, integers, date/times, UUID, URI, byte[], FileRef, custom datatypes.
JPA annotations: @ElementCollection, @CollectionTable, @OrderColumn
For example:
@ElementCollection
@CollectionTable(name = "PRODUCT_TAGS", joinColumns = @JoinColumn(name = "PRODUCT_ID"))
@Column(name = "TAG")
@OrderBy // optional sorting by the TAG column
private List<String> tags;
@ElementCollection
@CollectionTable(name = "PRODUCT_REORD_TAGS", joinColumns = @JoinColumn(name = "PRODUCT_ID"))
@Column(name = "TAG")
@OrderColumn(name = "ORDER_INDEX") // optional sorting by separate ORDER_INDEX column
private List<String> reorderableTags;
URI, FileRef, custom datatypes require explicit converter:
@ElementCollection
@CollectionTable(name = "PRODUCT_FILES", joinColumns = @JoinColumn(name = "PRODUCT_ID"))
@Column(name = "FILE_REF")
@Convert(converter = FileRefConverter.class)
private List<FileRef> fileRefs;
Not supported
- Map
- Collection of embeddable entities, enums.
Queries
Supported
"member of" operator for strict equality:
dataManager.load(Alpha.class)
.query("select e from Alpha e where :tag member of e.tags")
.parameter("tag", "tag1")
.list();
The same with join:
dataManager.load(Alpha.class)
.query("select e from Alpha e join e.tags t where t = :tag")
.parameter("tag", "tag1")
.list();
Join enables any comparison:
dataManager.load(Alpha.class)
.query("select e from Alpha e join e.tags t where t like :tag")
.parameter("tag", "t%1")
.list();
Check for empty:
dataManager.load(Alpha.class)
.query("select e from Alpha e where e.tags is empty")
.list();
Key-value queries:
dataManager.loadValues("select e.name, t from Alpha e join e.tags t")
.properties("name", "tag")
.list();
dataManager.loadValue("select t from Alpha e join e.tags t", String.class)
.list();
Not supported
"in" operator:
select e from Alpha e where :tag in (e.tags)
Conditions
"equal" searches for an equal element instead of comparing the whole collection:
dataManager.load(Alpha.class)
.condition(PropertyCondition.equal("tags", "tag1"))
.list();
// translated to `select e from Alpha e join e.tags t where t = :tag`
The same for "contains", it's translated to select e from Alpha e join e.tags t where t like :tag:
dataManager.load(Alpha.class)
.condition(PropertyCondition.contains("tags", "t%1"))
.list();
// translated to `select e from Alpha e join e.tags t where t like :tag`
Check for empty:
dataManager.load(Alpha.class)
.condition(PropertyCondition.isCollectionEmpty("tags", true))
.list();
Data repositories
Use @Query, for example:
public interface ProductRepository extends JmixDataRepository<Product, UUID> {
@Query("select p from Product p join p.tags t where t like :pattern")
List<Product> findByTagContaining(@Param("pattern") String pattern);
}
Fetching
Element collection attributes are lazily loaded by default. For eager loading, include the attribute in fetch plan.
UI
multiValuePicker, multiSelectComboBox, multiSelectComboBoxPicker components can be directly bound to an element collection attribute:
<multiValuePicker dataContainer="productDc" property="tags">
<actions>
<action id="value_selectAction" type="multi_value_select"/>
<action id="clearAction" type="value_clear"/>
</actions>
</multiValuePicker>
<multiSelectComboBox id="tagsComboBox" dataContainer="productDc" property="tags"/>
<multiSelectComboBoxPicker id="tagsComboBoxPicker" dataContainer="productDc" property="tags">
<actions>
<action id="clearAction_1" type="value_clear"/>
</actions>
</multiSelectComboBoxPicker>
Items of multiSelectComboBox and multiSelectComboBoxPicker can be set only programmatically:
@ViewComponent
private JmixMultiSelectComboBox<String> tagsComboBox;
@ViewComponent
private JmixMultiSelectComboBoxPicker<String> tagsComboBoxPicker;
@Subscribe
public void onInit(final InitEvent event) {
List<String> availableTags = List.of("t1", "t2", "t3", "t4");
tagsComboBox.setItems(availableTags);
tagsComboBoxPicker.setItems(availableTags);
Filter components support element collection attributes. All operations except "is empty" are applied to the individual items. For example, "Tags = tag1" will find all entities that have "tag1" item in their Tags collection.
"in interval" and "date equals" operations for date/time values are not supported.
Add-ons
The following subsystems and add-ons support element collections:
- Security
- Data Tools (Entity inspector)
- REST
- REST DataStore
- Audit
- Application Settings
- Search
Studio issue: https://youtrack.jmix.io/issue/JST-6288