Подтвердить что ты не робот

Ленивый выбор одного столбца (атрибут класса) с Hibernate

У меня есть старая таблица, с которой я работаю, которая выглядит так:

+------------------+--------------+------+-----+---------+-------+
| Field            | Type         | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+-------+
| BINARY_DATA_ID   | varchar(255) | NO   | PRI |         |       |
| BINARY_DATA      | longblob     | YES  |     | NULL    |       |
| BINARY_DATA_NAME | varchar(255) | YES  |     | NULL    |       |
+------------------+--------------+------+-----+---------+-------+

Основная проблема заключается в том, что класс BinaryData Java загружает столбец BINARY_DATA, даже если мне требуется только BINARY_DATA_NAME. Я знаю, что лучший способ архитектовать это состоит в том, чтобы разделить данные из метаданных (например, имя файла), чтобы они жили в отдельных таблицах. Оттуда это тривиально, чтобы сделать данные ленивыми. Вот как это должно было быть сделано в первую очередь.

К сожалению, для меня может оказаться невозможным сделать это из-за организационных ограничений. Как обходной путь, можно ли сделать эту колонку ленивой загрузкой, используя некоторые аннотации, вместо того, чтобы разделить вещи на отдельные таблицы? Я изменил класс BinaryData, так что он имеет внутренний статический класс BinaryDataData, который равен @Embedded, а атрибут @Basic(fetch=FetchType.LAZY):

@Entity
@Table
@Proxy(lazy=false)
@Inheritance(strategy=InheritanceType.JOINED)
public class BinaryData implements Serializable, Persistable<BinaryData>, Cloneable {

    private static final long serialVersionUID = /** blah */;

    @Id @Column @GeneratedValue(generator="uuid") @GenericGenerator(name="uuid", strategy="uuid")
    private String id;

    @Column
    private String binaryDataName;

    @Embedded
    @Basic(fetch = FetchType.LAZY)
    private BinaryDataData binaryData;

    @Transient
    private String cacheId;

    /**
     * Hibernate constructor
     */
    public BinaryData() { /* Creates a new instance of Attachment. */}

    public BinaryData(byte[] binaryData, String binaryDataName) {
        this.binaryData = new BinaryDataData(ArrayUtils.clone(binaryData));
        this.binaryDataName = binaryDataName;
    }

    /**
     * Returns the BinaryData byte stream.
     *
     * @return binaryData byte stream
     */
    @Embedded
    @Basic(fetch = FetchType.LAZY)
    public byte[] getBinaryData() {
        if (this.binaryData == null) {
            return new byte[0];
        }
        return binaryData.getActualData();
    }

    @Embeddable
    public static class BinaryDataData implements Serializable {
        @Column(length=32*1024*1024, columnDefinition="longblob", name="BINARY_DATA") @Lob
        private byte[] actualData;

        public BinaryDataData() { }

        public BinaryDataData(byte[] data) {
            this.actualData = data;
        }

        public byte[] getActualData() {
            if (this.actualData == null) {
                return new byte[0];
            }
            return this.actualData;
        }

        public void setBinaryData(byte[] newData) {
            this.actualData = newData;
        }

        @Override public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (!(obj instanceof BinaryDataData)) {
                return false;
            }
            final BinaryDataData other = (BinaryDataData) obj;
            if (!Arrays.equals(actualData, other.actualData)) {
                return false;
            }
            return true;
        }
    }

    /** onwards... */

К сожалению, это не сработает. SQL, который я вижу, все еще показывает полный выбор объекта, даже если двоичные данные не запрашиваются:

select ideaattach0_.BINARY_DATA_ID as BINARY1_9_, ideaattach0_1_.BINARY_DATA as BINARY2_9_, ideaattach0_1_.BINARY_DATA_NAME as BINARY3_9_, ideaattach0_.IDEA_BUCKET_ID as IDEA2_136_ from IDEA_ATTACHMENT ideaattach0_ inner join BINARY_DATA ideaattach0_1_ on ideaattach0_.BINARY_DATA_ID=ideaattach0_1_.BINARY_DATA_ID where ideaattach0_.BINARY_DATA_ID=?

Любые идеи? Спасибо.

4b9b3361

Ответ 1

От Hibernate, Глава 19. Повышение производительности:

Приобретение атрибута Lazy: атрибут или однозначная связь извлекается при доступе к переменной экземпляра. Для этого подхода требуется аппаратное обеспечение байт-кода времени сборки и редко требуется.

Ответ 2

Мне известно о дате этого запроса, однако я бы также попытался использовать класс проекционного значения, который отображает как подмножество столбцов, и использовать эту проекцию с указанным именованным запросом, который создает экземпляр этого объекта проекционного значения, вместо базового объекта, на который здесь ссылаются.

Я работаю над решением, которое использует этот метод, поэтому в настоящее время у меня нет полного примера. Однако основная идея заключается в том, что вы создадите запрос JPA, который использует синтаксис "select NEW Projection_Object_Target", где поля напрямую ссылаются на конструктор "Projection_Object_Target".

т.е. Используйте выражение конструктора следующим образом:

SELECT NEW fully.qualified.package.name.ProjectionObject(baseObject.column_target_0,baseObject.column_target_1,...,baseObject.column_target_n) FROM BaseObjectMappedInDBTable AS baseObject

Общий пример использования:

String queryStr =
  "SELECT NEW fully.qualified.package.name.ProjectionObject(baseObject.column_target_0) " +
  "FROM BaseObjectMappedInTable AS baseObject";
TypedQuery<ProjectionObject> query =
  em.createQuery(queryStr, ProjectionObject.class);
List<ProjectionObject> results = query.getResultList();

Ответ 3

Для проекта maven необходимо добавить следующую зависимость плагина в pom.xml:

<plugin>
    <groupId>org.hibernate.orm.tooling</groupId>
    <artifactId>hibernate-enhance-maven-plugin</artifactId>
    <version>${hibernate.version}</version>
    <executions>
        <execution>
            <configuration>
                <failOnError>true</failOnError>
                <enableLazyInitialization>true</enableLazyInitialization>
            </configuration>
            <goals>
                <goal>enhance</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Я проверил его в своем проекте, и он работает, пример объекта:

@Entity(name = "processing_record")
public class ProcessingRecord {

/**
 * Why uuid: https://www.clever-cloud.com/blog/engineering/2015/05/20/why-auto-increment-is-a-terrible-idea/
 */
@Id
@Column(name = "record_id")
@org.hibernate.annotations.Type(type = "pg-uuid")
private UUID id;

...

/**
 * Processing result.
 */
@Column(name = "result")
@Basic(fetch = FetchType.LAZY)
private String result;
...

Подробнее см. в следующей статье: LINK