【问题标题】:Spring-data-jpa storing blobspring-data-jpa 存储 blob
【发布时间】:2015-10-06 18:43:21
【问题描述】:

什么是使用 spring-data-jpa 存储带有 blob 的实体的“最佳”或规范方式?

@Entity
public class Entity {
  @Id
  private Long id;
  @Lob()
  private Blob blob;
}

public interface Repository extends CrudRepository<Entity,  Long> {
}

【问题讨论】:

  • 这个问题你解决了吗?这种情况我也有问题。我正在寻找答案。
  • 不适用于 Spring-data。如果我这样做了,我会与你分享。 ;)

标签: blob spring-data-jpa


【解决方案1】:

TL;博士

你可以看到sample project on my github。该项目展示了您如何将数据流式传输到/从数据库中。

问题

关于将@Lob 映射为byte[] 的所有建议都击败了(IMO)blob 的主要优势 - 流式传输。使用byte[],所有内容都会加载到内存中。可能没问题,但如果您选择 LargeObject,您可能想要直播。

解决方案

映射

@Entity
public class MyEntity {

    @Lob
    private Blob data;

    ...

}

配置

公开休眠 SessionFactoryCurrentSession 以便您可以获取LobCreator。在application.properties中:

spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext

将会话工厂公开为 bean:

@Bean // Need to expose SessionFactory to be able to work with BLOBs
public SessionFactory sessionFactory(HibernateEntityManagerFactory hemf) {
    return hemf.getSessionFactory();
}

创建 blob

@Service
public class LobHelper {

    private final SessionFactory sessionFactory;

    @Autowired
    public LobHelper(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Blob createBlob(InputStream content, long size) {
        return sessionFactory.getCurrentSession().getLobHelper().createBlob(content, size);
    }

    public Clob createClob(InputStream content, long size, Charset charset) {
        return sessionFactory.getCurrentSession().getLobHelper().createClob(new InputStreamReader(content, charset), size);
    }
}

另外 - 正如 cmets 中所指出的 - 只要您使用 @Blob 包括您获得的流,您就需要在事务中。只需标记工作部分@Transactional

【讨论】:

  • 它在休眠 5.2 中不起作用。 HibernateEntityManagerFactory 现已弃用。
  • 它可能仍然有效,但如果您阅读过时的 javadoc,很清楚需要做什么才能摆脱过时的类...
  • 阅读 Blob 怎么样?读取 Blob 需要打开底层 JDBC 连接,但 JPA 对此不提供任何保证,除非您的代码在事务中运行
  • 对,需要从事务中调用。这是个问题吗?不过好点,我应该提到它...
  • 一切都适用于 PostgreSQL。即使我将 Spring Boot 升级到最新版本 3.2.1-RELEASE(进行了一些修改)。对于 MariaDB,我无法让它工作(在两个版本中)。写入 DB 确实有效,但读取会产生 OutOfMemoryError。显然,blob 是立即从 DB 完全加载到内存中的。
【解决方案2】:

Spring Data 不处理 BLOB,但 Spring Content 可以。具体来说,Spring Content JPA 将内容作为 BLOB 存储在数据库中,并通过注释将该内容与实体相关联。

pom.xml

   <!-- Java API -->
   <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-jpa-boot-starter</artifactId>
      <version>0.0.11</version>
   </dependency>
   <!-- REST API -->
   <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-rest-boot-starter</artifactId>
      <version>0.0.11</version>
   </dependency>

Entity.java

@Entity
public class Entity {
   @Id
   @GeneratedValue
   private long id;

   @ContentId
   private String contentId;

   @ContentLength
   private long contentLength = 0L;

   // if you have rest endpoints
   @MimeType
   private String mimeType = "text/plain";

DataContentStore.java

@StoreRestResource(path="data")
public interface DataContentStore extends ContentStore<Data, String> {
}

与已接受答案相比,这种方法的优势在于开发人员无需担心任何样板代码(已接受答案中的“服务”)。 BLOB 也作为 Spring Resource 公开,提供自然的编程接口。或者可以通过 REST 接口自动导出。但是,除了 java config 和 store 接口之外,这些都不需要代表开发人员进行任何编码。

【讨论】:

  • 所以这非常适合存储 1 小时长的有问题的视频? @Paul Warren,随着内容变大,它会变慢吗?我说的是在存储后检索它。
  • 不应该@vilak。 Spring Content REST 通过应用服务器的内存空间流式传输视频,因此目的是将客户端连接到视频流,并让客户端将流从数据库中拉出,而不是将其存储在途中的任何地方。然而,特别是。当涉及到关系数据库时,它的工作情况通常取决于底层数据库客户端对 BLOB 流的支持程度。如果您考虑到特定的数据库/客户端,很高兴考虑优化(如果可能)。
【解决方案3】:

自动装配您的存储库接口并调用 save 方法传递您的实体对象。

我有一个类似的设置,效果很好:

@Autowired
Repository repository;

repository.save(entity);

@Entity
@Table(name = "something")
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Lob
    @Column
    private byte[] data;

【讨论】:

  • 你是如何创建 Blob 的?
  • 请找到我修改后的答案
  • 这违背了流式传输的目的,您可能会遇到更大数据的OOM。
【解决方案4】:

您可以通过使用 Hibernate.getLobCreator 并传递 EntityManager 可以解包给您的会话的单个语句(下面的 4 个)来完成:

// 1. Get entity manager and repository
EntityManager em = .... // get/inject someway the EntityManager
EntityRepository repository = ...// get/inject your Entity repository

// 2. Instantiate your Entity
Entity entity = new Entity();

// 3. Get an input stream (you shall also know its length)
File inFile = new File("/somepath/somefile");
InputStream inStream = new FileInputStream(inFile);

// 4. Now copy to the BLOB
Blob blob =
  Hibernate.getLobCreator(em.unwrap(Session.class))
           .createBlob(inStream, inFile.length());

// 5. And finally save the BLOB
entity.setBlob(blob);
entityRepository.save(f);

【讨论】:

  • 当然,但您的回答中根本没有使用 spring-data-jpa ;)
  • 完全没有? EntityManager 和 Repository 是什么?对 Hibernate 的唯一调用是提供一个足够的 Blob,它依赖于实现。要保持 100% 独立,您可以通过 Spring 注入这个单一语句。
【解决方案5】:

您也可以直接从DataSource 创建Blob

@Component
public class LobHelper {

    private final DataSource ds;

    public LobHelper(@Autowired DataSource ds){
         this.ds = ds;
    }

    public Blob createBlob(byte[] content) {
        try (Connection conn = ds.getConnection()) {
            Blob b = conn.createBlob();
            try (OutputStream os = b.setBinaryStream(1);
                 InputStream is = new ByteArrayInputStream(content)) {
                byte[] buffer = new byte[500000];
                int len;
                while ((len = is.read(buffer)) > 0) {
                    os.write(buffer, 0, len);
                }
                return b;
            }
        } catch (Exception e) {
            log.error("Error while creating blob.", e);
        }
        return null;
    }

}

【讨论】:

  • 为什么需要字节数组而不是流?为什么不使用 try-with-resources?
  • @user482745 随时重构此代码。就我而言,我需要使用 byte[] 而不是流,但更改它非常简单。我不想嵌套 try-with-resources 语句,但在这里 - 我重构了这段代码。
【解决方案6】:

我在获取当前会话工厂时遇到了一些问题,例如上面的答案(例如错误:Could not obtain transaction-synchronized Session for current threadno transaction is in progress)。最后(在 Spring Boot 应用程序中,目前是 2.3.1.RELEASE 版本,Hibernate 5.4.1)我正在使用下面的方法,我的问题已经解决了。

@Component
public class SomeService {

    /**
     * inject entity manager
     */
    @PersistenceContext 
    private EntityManager entityManager;

    @Transactional
    public void storeMethod(File file) {
       // ...
       FileInputStream in = new FileInputStream(file);

       Session session = entityManager.unwrap(Session.class);
       Blob blob = session.getLobHelper().createBlob(in, file.length());
       // ...
       entity.setData(blob);
       repo.save(entity);
    }
}

LobHelper 可能是这样的:

@Service
public class LobHelper {

    @PersistenceContext
    private EntityManager entityManager;

    public Blob createBlob(InputStream content, long size) {
        return ((Session)entityManager).getLobHelper().createBlob(content, size);
    }

    // ...

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-26
    • 1970-01-01
    • 2018-08-06
    • 2016-12-26
    • 1970-01-01
    • 2018-10-06
    • 2018-06-15
    • 1970-01-01
    相关资源
    最近更新 更多