What is \"best\" or canonical way to store entity with blob using spring-data-jpa?
@Entity
public class Entity {
@Id
private Long id;
@Lob()
private Blob
You can do it with a single statement (4 below) using Hibernate.getLobCreator and passing the session that EntityManager can unwrap to you:
// 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);
You can see sample project on my github. The project shows how you stream data to/from database.
All advices about mapping the @Lob
as byte[]
defeats (IMO) the main advantage of blobs - streaming. With byte[]
everything gets loaded in memory. May be ok but if you go with LargeObject you likely want to stream.
@Entity
public class MyEntity {
@Lob
private Blob data;
...
}
Expose hibernate SessionFactory and CurrentSession so you can get hold of the LobCreator. In application.properties:
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
Expose session factory as bean:
@Bean // Need to expose SessionFactory to be able to work with BLOBs
public SessionFactory sessionFactory(HibernateEntityManagerFactory hemf) {
return hemf.getSessionFactory();
}
@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);
}
}
Also - as pointed out in comments - as long as you work with the @Blob
including the stream you get you need to be within transaction. Just mark the working part @Transactional
.
Autowire your repository interface and call the save method passing your entity object.
I have a similar setup which works pretty well:
@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;
You can also create Blob
right from DataSource
:
@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;
}
}
Spring Data does not handle BLOBs but Spring Content does. Specifically, Spring Content JPA stores content as BLOBs in the database and associates that content with an Entity through annotations.
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> {
}
The advantage of this approach over the accepted answer is that the Developer doesn't need to worry about any of the boilerplate code (the "service" in the accepted answer). The BLOB is also exposed as Spring Resource giving a natural programming interface. Or can be automatically exported via a REST interface. But none of this requires any coding, on behalf of the Developer, besides java config and the store interface.
I had a little problems to get current session factory like in answers above (got for example errors like: Could not obtain transaction-synchronized Session for current thread
) . Finally (in Spring Boot application, currently 2.3.1.RELEASE version, Hibernate 5.4.1) i'm using approach like below and my problems has solved.
@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);
}
}