The goal of this project is to make it easy to store binary data alongside the entities that refer to them.
For example:
-
An avatar image that is linked to the
User
entity -
Attachments that are linked to an
Issue
in an issue tracker
Use this dependency if you use Maven:
<dependency>
<groupId>io.github.wimdeblauwe</groupId>
<artifactId>biob</artifactId>
<version>${biob.version}</version>
</dependency>
The library is not tied to Spring Boot, but I will use some concepts of Spring Boot to explain it usage.
Suppose you have the following User
entity:
@Entity
public class User {
@Id
private Long id;
private String username;
// further details of class omitted...
}
If you use Spring Data, you will have a repository to persist this in the database:
public interface UserRepository extends CrudRepository<User, Long> {
}
and most likely a Service
that uses this repository:
@Service
@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository repository;
public UserServiceImpl(UserRepository repository) {
this.repository = repository;
}
public void createUser( String username ) {
repository.save( new User(null, username) );
}
}
We now extend the User
class to store the avatar image of the user. However,
we will not store binary file in the database, but only store a reference to
the file in the database. The file itself will be stored in a BinaryObjectStorage
implementation of which our library has various implementations available.
The type of the reference in the entity can be any primitive or class you want. For
this example, we will go for a simple UUID
:
@Entity
public class User {
@Id
private Long id;
private String username;
private UUID avatarId;
// further details of class omitted...
}
To store a binary file, we create an instance of BinaryObjectRepository
:
InMemoryBinaryObjectStorage storage = new InMemoryBinaryObjectStorage(); (1)
BinaryObjectRepository<User, UUID> repository = new BinaryObjectRepository<>( (2)
UUID::randomUUID, (3)
(user, uuid) -> user.getId() + "/images/"
+ uuid.toString(), (4)
storage); (5)
-
Creates in memory instance of
BinaryObjectStorage
-
The
BinaryObjectRepository
needs 2 generic types. The first one is the type of the entity that the binary object is refered from (e.g.User
). The second one is the type that is used by the reference itself (e.g.UUID
). -
The first argument is a function that generates a new object of the type of the reference. This this case, we generate a random
UUID
. -
The second argument is a function that generates the path where the file should be stored. This path will be relative and interpreted by the
BinaryObjectStorage
implementation as they see fit. -
The actual
BinaryObjectStorage
that will persist the file. This can be in memory, in a folder structure on disk, on an S3 bucket, …
There are now 2 ways to use the BinaryObjectRepository
in our service:
-
Inject the full
BinaryObjectRepository
instance into your service. -
Inject the
BinaryObjectStorage
and create theBinaryObjectRepository
in the constructor of your service.
This is an example where we inject the backing storage in our service:
@Service
@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository repository;
private final BinaryObjectRepository objectRepository;
public UserServiceImpl(UserRepository repository,
BinaryObjectStorage storage) {
this.repository = repository;
this.objectRepository = new BinaryObjectRepository<>( UUID::randomUUID, (1)
(user, uuid) -> user.getId() + "/images/"
+ uuid.toString(),
storage);
}
public void createUser( String username, MultipartFile avatar ) {
User user = repository.save( new User(null, username) );
UUID avatarId = objectRepository.store( user, (2)
getMetadata(avatar),
avatar.getInputStream() );
user.setAvatarId( avatarId ); (3)
}
private BinaryObjectMetadata getMetadata(MultipartFile multipartFile) { (4)
return new BinaryObjectMetadata(multipartFile.getSize(),
multipartFile.getOriginalFilename(),
multipartFile.getContentType());
}
}
-
Create the
BinaryObjectRepository
in the constructor -
Store the binary file. We assume it was uploaded as a
MultipartFile
via a@Controller
for example. -
Use the returned
avatarId
and set it on the entity so it is stored in the database along with theUser
entity. -
The
store()
method also requires some metadata with is gathered in theBinaryObjectMetadata
object.
The project currently has the following backing storages implemented:
The InMemoryBinaryObjectStorage
keeps all binary objects in memory. Its main
purpose is testing.
-
Builds are done on Travis: https://travis-ci.org/wimdeblauwe/biob
-
Code quality is available via SonarQube: https://sonarcloud.io/dashboard?id=org.wimdeblauwe%3Abiob
-
SNAPSHOT versions are put on https://oss.sonatype.org/content/repositories/snapshots
-
All releases can be downloaded from https://oss.sonatype.org/content/groups/public