とあるシステムにおいて、「エンティティの属性について、テーブルにカラムがある場合のみマッピングを行う」必要が出てきました。
要するに、
@Entity public class Sample { @Column private int foo; // テーブルにbarカラムがない場合は何もしない @Column @OptionalColumn private int bar; ... }
のようなエンティティを使いたいということです。
おそらく標準の機能では不可能ですが、EclipseLink限定で次のようにすれば可能です。
list()
とかset()
は自作のJava言語拡張ライブラリIndolentlyを使用しています。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface OptionalColumn { } public class OptionalColumnListener extends SessionEventAdapter, SessionCustomizer { // 自分自身をセッションリスナーに追加 @Override public void customize(final Session session) throws Exception { session.getEventManager().addListener(new OptionalColumnListener()); } @Override public void postLogin(final SessionEvent event) { final Session session = event.getSession(); try (Connection conn = (Connection) session.getDatasourceLogin().connectToDatasource(null, session)) { for (final ClassDescriptor cd : session.getDescriptors().values()) { this.removeFields(conn, cd); } } catch (final SQLException e) { throw new RuntimeException(e); } } private void removeFields(final Connection conn, final ClassDescriptor cd) throws SQLException { // テーブルのカラム一覧 final SSet<String> columns = this.getColumnNames(conn, cd.getTableName()); for (final DatabaseMapping mapping : list(cd.getMappings())) { final DatabaseField dbField = mapping.getField(); if (dbField == null) { // 1-1や1-N関連で外部キーカラムが自テーブル側に無いケースなので無視 continue; } // エンティティの属性名 final String fieldName = mapping.getAttributeName(); // テーブルのカラム名 final String columnName = dbField.getName(); // エンティティの属性 final Field field = FieldUtil.getField(cd.getJavaClass(), fieldName); // OptionalColumnアノテーションがあり、テーブルにカラムが宣言されていない場合はマッピングを削除する if ((field.getAnnotation(OptionalColumn.class) != null) && columns.none(columnName::equalsIgnoreCase)) { this.removeField(cd, dbField, fieldName, columnName); } } } // 指定したカラムに関係するマッピングを全て削除 private void removeField(final ClassDescriptor cd, final DatabaseField dbField, final String fieldName, final String columnName) { cd.removeMappingForAttributeName(fieldName); this.removeField(columnName, cd.getFields()); this.removeField(columnName, cd.getAllFields()); this.removeField(columnName, cd.getSelectionFields()); cd.getObjectBuilder().getFieldsMap().remove(dbField); // protectedフィールドなので無理やり取得 final Map<String, DatabaseField> mappingsByAttr = cast(FieldUtil.getFieldValue(cd.getObjectBuilder(), "mappingsByAttribute")); mappingsByAttr.remove(fieldName); System.out.println(String.format("remove: %s.%s -> %s.%s", cd.getJavaClass().getSimpleName(), fieldName, cd.getTableName(), columnName)); } private void removeField(final String columnName, final List<? extends DatabaseField> fields) { fields.removeAll(list(fields).filter(x -> x.getName().equalsIgnoreCase(columnName))); } // テーブルのカラム名一覧を取得する private SSet<String> getColumnNames(final Connection conn, final String tableName) throws SQLException { final SSet<String> columns = set(); try (ResultSet rs = conn.getMetaData().getColumns(null, null, tableName, null)) { while (rs.next()) { columns.add(rs.getString("COLUMN_NAME")); } } return columns; } }
恐ろしく不親切な解説ですが、まあわかる人がわかれば良いということで。