Refactored database implementation to further hide implementation details from other classes and allow for drop-in replacement.

This commit is contained in:
neviyn 2016-03-18 19:09:58 +00:00
parent 5e80829db0
commit e8633aa0e1
7 changed files with 128 additions and 151 deletions

View File

@ -5,8 +5,7 @@ import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import musicplayer.callbacks.LibraryCallbackInterface; import musicplayer.callbacks.LibraryCallbackInterface;
import musicplayer.callbacks.PlayerCallbackInterface; import musicplayer.callbacks.PlayerCallbackInterface;
import musicplayer.db.DatabaseManager; import musicplayer.db.IDatabase;
import musicplayer.db.Gateway;
import musicplayer.model.Album; import musicplayer.model.Album;
import musicplayer.model.Artist; import musicplayer.model.Artist;
import musicplayer.model.HasSongs; import musicplayer.model.HasSongs;
@ -52,6 +51,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
private JSlider seekBar; private JSlider seekBar;
private final JComboBox<String> libraryDisplayType = new JComboBox<>(); private final JComboBox<String> libraryDisplayType = new JComboBox<>();
private final IPlayer player; private final IPlayer player;
private final IDatabase database;
private final PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>()); private final PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>());
private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode(); private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode();
private boolean libraryUpdating = false; private boolean libraryUpdating = false;
@ -60,8 +60,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
private final Map<String, Runnable> libraryDisplayVariants = createDisplayVariantMap(); private final Map<String, Runnable> libraryDisplayVariants = createDisplayVariantMap();
@Inject @Inject
public PlayerGUI(IPlayer player) { public PlayerGUI(IPlayer player, IDatabase database) {
this.player = player; this.player = player;
this.database = database;
createUI(); createUI();
resetTree(); resetTree();
Thread seekBarUpdater = new Thread(() -> { Thread seekBarUpdater = new Thread(() -> {
@ -102,7 +103,6 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
} }
public static void main(String[] args) { public static void main(String[] args) {
DatabaseManager.init();
Injector injector = Guice.createInjector(); Injector injector = Guice.createInjector();
PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class); PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class);
JFrame frame = new JFrame(); JFrame frame = new JFrame();
@ -269,7 +269,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
addNodeToTreeModel(model, parentNode, updatingNode); addNodeToTreeModel(model, parentNode, updatingNode);
libraryView.setModel(model); libraryView.setModel(model);
List<Path> dirs = ConfigManager.getLibraryDirectories().stream().map(File::toPath).collect(Collectors.toList()); List<Path> dirs = ConfigManager.getLibraryDirectories().stream().map(File::toPath).collect(Collectors.toList());
LibraryUtils.processSongsWithCallback(dirs, this); LibraryUtils.processSongsWithCallback(database, dirs, this);
} }
@Override @Override
@ -290,9 +290,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
*/ */
private Map<String, Runnable> createDisplayVariantMap() { private Map<String, Runnable> createDisplayVariantMap() {
Map<String, Runnable> value = new HashMap<>(); Map<String, Runnable> value = new HashMap<>();
value.put("Song", () -> Gateway.listAllT(Song.class).ifPresent(this::populateLibraryWithSongsOnly)); value.put("Song", () -> database.listAllT(Song.class).ifPresent(this::populateLibraryWithSongsOnly));
value.put("Album/Song", () -> Gateway.listAllT(Album.class).ifPresent(this::populateLibraryWithGroupedSongs)); value.put("Album/Song", () -> database.listAllT(Album.class).ifPresent(this::populateLibraryWithGroupedSongs));
value.put("Artist/Song", () -> Gateway.listAllT(Artist.class).ifPresent(this::populateLibraryWithGroupedSongs)); value.put("Artist/Song", () -> database.listAllT(Artist.class).ifPresent(this::populateLibraryWithGroupedSongs));
return value; return value;
} }

View File

@ -1,82 +0,0 @@
package musicplayer.db;
import musicplayer.ConfigManager;
import musicplayer.model.Album;
import musicplayer.model.Artist;
import musicplayer.model.Song;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.Properties;
/**
* Singleton for managing connection to Hibernate.
*/
public class DatabaseManager {
private static final DatabaseManager ourInstance = new DatabaseManager();
private SessionFactory sessionFactory;
public static DatabaseManager getInstance() {
return ourInstance;
}
private DatabaseManager() {
}
/**
* Open a SessionFactory to the database as defined in hibernate.cfg.xml
*/
public static void init() {
if (getInstance().sessionFactory != null)
getInstance().sessionFactory.close();
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
properties.put("hibernate.connection.driver_class", "org.hsqldb.jdbc.JDBCDriver");
properties.put("hibernate.connection.url", ConfigManager.getDatabaseConnectionString());
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.enable_lazy_load_no_trans", "true");
getInstance().sessionFactory = new Configuration()
.addProperties(properties)
.addAnnotatedClass(Album.class)
.addAnnotatedClass(Artist.class)
.addAnnotatedClass(Song.class)
.buildSessionFactory();
}
/**
* Open a SessionFactory to an in-memory database for testing
*/
public static void testMode() {
if (getInstance().sessionFactory != null)
getInstance().sessionFactory.close();
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
properties.put("hibernate.connection.driver_class", "org.hsqldb.jdbc.JDBCDriver");
properties.put("hibernate.connection.url", "jdbc:hsqldb:mem:.");
properties.put("hibernate.hbm2ddl.auto", "create-drop");
properties.put("hibernate.enable_lazy_load_no_trans", "true");
getInstance().sessionFactory = new Configuration()
.addProperties(properties)
.addAnnotatedClass(Album.class)
.addAnnotatedClass(Artist.class)
.addAnnotatedClass(Song.class)
.buildSessionFactory();
}
/**
* @return Session for database interaction.
*/
public Session getSession() {
return sessionFactory.openSession();
}
@SuppressWarnings("CloneDoesntCallSuperClone")
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}

View File

@ -1,26 +1,77 @@
package musicplayer.db; package musicplayer.db;
import musicplayer.ConfigManager;
import musicplayer.model.*; import musicplayer.model.*;
import musicplayer.util.AlbumArtExtractor; import musicplayer.util.AlbumArtExtractor;
import org.hibernate.Criteria; import org.hibernate.Criteria;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.Restrictions;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Properties;
/** /**
* Contains database queries. * Class for managing connection to Hibernate.
*/ */
public class Gateway { class HibernateDatabase implements IDatabase{
private static SessionFactory sessionFactory;
/**
* Create a database connection.
*/
public HibernateDatabase() {
this(false);
}
/**
* Create a database connection.
* @param testMode Enable test mode.
*/
public HibernateDatabase(boolean testMode){
Properties properties = new Properties();
if (sessionFactory != null)
sessionFactory.close();
if(testMode){
properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
properties.put("hibernate.connection.driver_class", "org.hsqldb.jdbc.JDBCDriver");
properties.put("hibernate.connection.url", "jdbc:hsqldb:mem:.");
properties.put("hibernate.hbm2ddl.auto", "create-drop");
properties.put("hibernate.enable_lazy_load_no_trans", "true");
}
else{
properties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
properties.put("hibernate.connection.driver_class", "org.hsqldb.jdbc.JDBCDriver");
properties.put("hibernate.connection.url", ConfigManager.getDatabaseConnectionString());
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.enable_lazy_load_no_trans", "true");
}
sessionFactory = new Configuration()
.addProperties(properties)
.addAnnotatedClass(Album.class)
.addAnnotatedClass(Artist.class)
.addAnnotatedClass(Song.class)
.buildSessionFactory();
}
/**
* @return Session for database interaction.
*/
private Session getSession() {
return sessionFactory.openSession();
}
/** /**
* @param name Name of the album to find. * @param name Name of the album to find.
* @return Album found with name or Optional.empty() * @return Album found with name or Optional.empty()
*/ */
public static Optional<Album> getOneAlbum(String name) { public Optional<Album> getOneAlbum(String name) {
try (Session session = DatabaseManager.getInstance().getSession()) { try (Session session = getSession()) {
Criteria criteria = session.createCriteria(Album.class); Criteria criteria = session.createCriteria(Album.class);
Album album = (Album) criteria.add(Restrictions.eq("name", name)).uniqueResult(); Album album = (Album) criteria.add(Restrictions.eq("name", name)).uniqueResult();
return (album == null) ? Optional.empty() : Optional.of(album); return (album == null) ? Optional.empty() : Optional.of(album);
@ -31,8 +82,8 @@ public class Gateway {
* @param name Name of the artist to find. * @param name Name of the artist to find.
* @return Artist found with name or Optional.empty() * @return Artist found with name or Optional.empty()
*/ */
public static Optional<Artist> getOneArtist(String name) { public Optional<Artist> getOneArtist(String name) {
try (Session session = DatabaseManager.getInstance().getSession()) { try (Session session = getSession()) {
Criteria criteria = session.createCriteria(Artist.class); Criteria criteria = session.createCriteria(Artist.class);
Artist artist = (Artist) criteria.add(Restrictions.eq("name", name)).uniqueResult(); Artist artist = (Artist) criteria.add(Restrictions.eq("name", name)).uniqueResult();
return (artist == null) ? Optional.empty() : Optional.of(artist); return (artist == null) ? Optional.empty() : Optional.of(artist);
@ -43,8 +94,8 @@ public class Gateway {
* @param metadata Metadata of song to find. * @param metadata Metadata of song to find.
* @return Song found with metadata or Optional.empty() * @return Song found with metadata or Optional.empty()
*/ */
public static Optional<Song> getOneSong(ExtractedMetadata metadata){ public Optional<Song> getOneSong(ExtractedMetadata metadata){
try (Session session = DatabaseManager.getInstance().getSession()) { try (Session session = getSession()) {
Song song = (Song)session.createCriteria(Song.class) Song song = (Song)session.createCriteria(Song.class)
.add(Restrictions.eq("songFile", metadata.getSongFile())).uniqueResult(); .add(Restrictions.eq("songFile", metadata.getSongFile())).uniqueResult();
return (song == null) ? Optional.empty() : Optional.of(song); return (song == null) ? Optional.empty() : Optional.of(song);
@ -56,8 +107,8 @@ public class Gateway {
* @param typeClass Class representing the type of items to find. * @param typeClass Class representing the type of items to find.
* @return List of all items in the database of class typeClass. * @return List of all items in the database of class typeClass.
*/ */
public static <T extends IDBType> Optional<List<T>> listAllT(Class<T> typeClass){ public <T extends IDBType> Optional<List<T>> listAllT(Class<T> typeClass){
try (Session session = DatabaseManager.getInstance().getSession()) { try (Session session = getSession()) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<T> output = session.createCriteria(typeClass).list(); List<T> output = session.createCriteria(typeClass).list();
return (output == null || output.isEmpty()) ? Optional.empty() : Optional.of(output); return (output == null || output.isEmpty()) ? Optional.empty() : Optional.of(output);
@ -69,8 +120,8 @@ public class Gateway {
* If the song already exists it will be updated instead. * If the song already exists it will be updated instead.
* @param metadata New song information. * @param metadata New song information.
*/ */
public static void addSong(ExtractedMetadata metadata) { public void addSong(ExtractedMetadata metadata) {
try (Session session = DatabaseManager.getInstance().getSession()) { try (Session session = getSession()) {
session.beginTransaction(); session.beginTransaction();
Optional<Album> albumObj = getOneAlbum(metadata.getAlbum()); Optional<Album> albumObj = getOneAlbum(metadata.getAlbum());
if (!albumObj.isPresent()) { if (!albumObj.isPresent()) {
@ -92,4 +143,12 @@ public class Gateway {
session.getTransaction().commit(); session.getTransaction().commit();
} }
} }
public void addSong(Song song){
try (Session session = getSession()) {
session.beginTransaction();
session.save(song);
session.getTransaction().commit();
}
}
} }

View File

@ -0,0 +1,17 @@
package musicplayer.db;
import com.google.inject.ImplementedBy;
import musicplayer.model.*;
import java.util.List;
import java.util.Optional;
@ImplementedBy(HibernateDatabase.class)
public interface IDatabase {
Optional<Album> getOneAlbum(String name);
Optional<Artist> getOneArtist(String name);
Optional<Song> getOneSong(ExtractedMetadata metadata);
<T extends IDBType> Optional<List<T>> listAllT(Class<T> typeClass);
void addSong(ExtractedMetadata metadata);
void addSong(Song song);
}

View File

@ -13,7 +13,7 @@ import org.gstreamer.elements.PlayBin2;
import java.io.File; import java.io.File;
import java.util.Optional; import java.util.Optional;
public class GStreamerPlayer implements IPlayer{ class GStreamerPlayer implements IPlayer{
private final PlayBin2 playBin; private final PlayBin2 playBin;

View File

@ -1,7 +1,7 @@
package musicplayer.util; package musicplayer.util;
import musicplayer.callbacks.LibraryCallbackInterface; import musicplayer.callbacks.LibraryCallbackInterface;
import musicplayer.db.Gateway; import musicplayer.db.IDatabase;
import musicplayer.model.ExtractedMetadata; import musicplayer.model.ExtractedMetadata;
import org.jaudiotagger.audio.AudioFileIO; import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException; import org.jaudiotagger.audio.exceptions.CannotReadException;
@ -26,7 +26,7 @@ public final class LibraryUtils {
* @param rootDirectory Folder(s) containing music files to index. * @param rootDirectory Folder(s) containing music files to index.
* @param callbackInterface Object to send callback data to, set to null if the application doesn't require update data. * @param callbackInterface Object to send callback data to, set to null if the application doesn't require update data.
*/ */
public static void processSongsWithCallback(List<Path> rootDirectory, LibraryCallbackInterface callbackInterface){ public static void processSongsWithCallback(IDatabase database, List<Path> rootDirectory, LibraryCallbackInterface callbackInterface){
boolean callbacksEnabled = callbackInterface != null; boolean callbacksEnabled = callbackInterface != null;
updaterThread = new Thread(() -> { updaterThread = new Thread(() -> {
rootDirectory.forEach(dir -> { rootDirectory.forEach(dir -> {
@ -34,7 +34,7 @@ public final class LibraryUtils {
Files.walk(dir) Files.walk(dir)
.filter(f -> f.toString().matches(musicFileExtensionRegex)).map(LibraryUtils::autoParse) .filter(f -> f.toString().matches(musicFileExtensionRegex)).map(LibraryUtils::autoParse)
.forEach(x -> x.ifPresent(i -> { .forEach(x -> x.ifPresent(i -> {
Gateway.addSong(i); database.addSong(i);
if (callbacksEnabled) if (callbacksEnabled)
callbackInterface.currentlyUpdating(i.toString()); callbackInterface.currentlyUpdating(i.toString());
})); }));

View File

@ -4,11 +4,9 @@ import musicplayer.model.Album;
import musicplayer.model.Artist; import musicplayer.model.Artist;
import musicplayer.model.ExtractedMetadata; import musicplayer.model.ExtractedMetadata;
import musicplayer.model.Song; import musicplayer.model.Song;
import org.hibernate.Session;
import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag; import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.id3.ID3v23Tag; import org.jaudiotagger.tag.id3.ID3v23Tag;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -20,46 +18,37 @@ import static org.junit.Assert.assertTrue;
public class GatewayTest { public class GatewayTest {
private Session session; private IDatabase database;
@Before @Before
public void setUp(){ public void setUp(){
DatabaseManager.testMode(); database = new HibernateDatabase(true);
session = DatabaseManager.getInstance().getSession();
}
@After
public void tearDown(){
session.close();
session = null;
} }
@Test @Test
public void testGetOneAlbum() throws Exception { public void testGetOneAlbum() throws Exception {
String albumName = "Test Album"; String albumName = "Test Album";
Album testAlbum = new Album(albumName); Album testAlbum = new Album(albumName);
session.beginTransaction(); Song song = new Song("1", "1", "s1", new Artist("art"), testAlbum, "", "");
session.save(testAlbum); database.addSong(song);
session.getTransaction().commit(); assertTrue(database.getOneAlbum(albumName).isPresent());
assertTrue(Gateway.getOneAlbum(albumName).isPresent()); Album retrievedAlbum = database.getOneAlbum(albumName).get();
Album retrievedAlbum = Gateway.getOneAlbum(albumName).get();
assertEquals(testAlbum, retrievedAlbum); assertEquals(testAlbum, retrievedAlbum);
} }
@Test @Test
public void testGetOneAlbumEmptyDB() throws Exception { public void testGetOneAlbumEmptyDB() throws Exception {
assertTrue(!Gateway.getOneAlbum("").isPresent()); assertTrue(!database.getOneAlbum("").isPresent());
} }
@Test @Test
public void testGetOneArtist() throws Exception { public void testGetOneArtist() throws Exception {
String artistName = "Test Artist"; String artistName = "Test Artist";
Artist testArtist = new Artist(artistName); Artist testArtist = new Artist(artistName);
session.beginTransaction(); Song song = new Song("1", "1", "s1", testArtist, new Album("alb"), "", "");
session.save(testArtist); database.addSong(song);
session.getTransaction().commit(); assertTrue(database.getOneArtist(artistName).isPresent());
assertTrue(Gateway.getOneArtist(artistName).isPresent()); Artist retrievedArtist = database.getOneArtist(artistName).get();
Artist retrievedArtist = Gateway.getOneArtist(artistName).get();
assertEquals(testArtist, retrievedArtist); assertEquals(testArtist, retrievedArtist);
} }
@ -70,12 +59,10 @@ public class GatewayTest {
Song song1 = new Song("1", "1", "s1", artist1, new Album("a"), "", ""); Song song1 = new Song("1", "1", "s1", artist1, new Album("a"), "", "");
Song song2 = new Song("2", "1", "s2", artist1, new Album("b"), "", ""); Song song2 = new Song("2", "1", "s2", artist1, new Album("b"), "", "");
Song song3 = new Song("1", "1", "t1", artist2, new Album("c"), "", ""); Song song3 = new Song("1", "1", "t1", artist2, new Album("c"), "", "");
session.beginTransaction(); database.addSong(song1);
session.save(song1); database.addSong(song2);
session.save(song2); database.addSong(song3);
session.save(song3); List<Artist> retrievedArtists = database.listAllT(Artist.class).get();
session.getTransaction().commit();
List<Artist> retrievedArtists = Gateway.listAllT(Artist.class).get();
int index1 = retrievedArtists.indexOf(artist1); int index1 = retrievedArtists.indexOf(artist1);
int index2 = retrievedArtists.indexOf(artist2); int index2 = retrievedArtists.indexOf(artist2);
assertTrue(retrievedArtists.get(index1).getSongs().size() == 2); assertTrue(retrievedArtists.get(index1).getSongs().size() == 2);
@ -87,7 +74,7 @@ public class GatewayTest {
@Test @Test
public void testGetOneArtistEmptyDB() throws Exception { public void testGetOneArtistEmptyDB() throws Exception {
assertTrue(!Gateway.getOneArtist("").isPresent()); assertTrue(!database.getOneArtist("").isPresent());
} }
@Test @Test
@ -95,13 +82,11 @@ public class GatewayTest {
Song song1 = new Song("1", "1", "s1", new Artist("a"), new Album("a"), "", ""); Song song1 = new Song("1", "1", "s1", new Artist("a"), new Album("a"), "", "");
Song song2 = new Song("2", "1","s2", new Artist("b"), new Album("a"), "", ""); Song song2 = new Song("2", "1","s2", new Artist("b"), new Album("a"), "", "");
Song song3 = new Song("1", "1","t1", new Artist("c"), new Album("b"), "", ""); Song song3 = new Song("1", "1","t1", new Artist("c"), new Album("b"), "", "");
session.beginTransaction(); database.addSong(song1);
session.save(song1); database.addSong(song2);
session.save(song2); database.addSong(song3);
session.save(song3); assertTrue(database.listAllT(Song.class).isPresent());
session.getTransaction().commit(); List<Song> result = database.listAllT(Song.class).get();
assertTrue(Gateway.listAllT(Song.class).isPresent());
List<Song> result = Gateway.listAllT(Song.class).get();
assertTrue(result.size() == 3); assertTrue(result.size() == 3);
assertTrue(result.contains(song1)); assertTrue(result.contains(song1));
assertTrue(result.contains(song2)); assertTrue(result.contains(song2));
@ -110,7 +95,7 @@ public class GatewayTest {
@Test @Test
public void testListAllSongsEmptyDB() throws Exception { public void testListAllSongsEmptyDB() throws Exception {
assertTrue(!Gateway.listAllT(Song.class).isPresent()); assertTrue(!database.listAllT(Song.class).isPresent());
} }
@Test @Test
@ -120,12 +105,10 @@ public class GatewayTest {
Song song1 = new Song("1", "1", "s1", new Artist("a"), album1, "", ""); Song song1 = new Song("1", "1", "s1", new Artist("a"), album1, "", "");
Song song2 = new Song("2", "1", "s2", new Artist("b"), album1, "", ""); Song song2 = new Song("2", "1", "s2", new Artist("b"), album1, "", "");
Song song3 = new Song("1", "1", "t1", new Artist("c"), album2, "", ""); Song song3 = new Song("1", "1", "t1", new Artist("c"), album2, "", "");
session.beginTransaction(); database.addSong(song1);
session.save(song1); database.addSong(song2);
session.save(song2); database.addSong(song3);
session.save(song3); List<Album> retrievedAlbums = database.listAllT(Album.class).get();
session.getTransaction().commit();
List<Album> retrievedAlbums = Gateway.listAllT(Album.class).get();
int index1 = retrievedAlbums.indexOf(album1); int index1 = retrievedAlbums.indexOf(album1);
int index2 = retrievedAlbums.indexOf(album2); int index2 = retrievedAlbums.indexOf(album2);
assertTrue(retrievedAlbums.get(index1).getSongs().size() == 2); assertTrue(retrievedAlbums.get(index1).getSongs().size() == 2);
@ -145,8 +128,8 @@ public class GatewayTest {
tags.addField(FieldKey.GENRE, "Test Genre"); tags.addField(FieldKey.GENRE, "Test Genre");
tags.addField(FieldKey.DISC_NO, "100"); tags.addField(FieldKey.DISC_NO, "100");
ExtractedMetadata metadata = new ExtractedMetadata(tags, new File("")); ExtractedMetadata metadata = new ExtractedMetadata(tags, new File(""));
Gateway.addSong(metadata); database.addSong(metadata);
Song song = (Song)session.createCriteria(Song.class).uniqueResult(); Song song = database.getOneSong(metadata).get();
assertEquals(song.getAlbum().getName(), metadata.getAlbum()); assertEquals(song.getAlbum().getName(), metadata.getAlbum());
assertEquals(song.getArtist().getName(), metadata.getArtist()); assertEquals(song.getArtist().getName(), metadata.getArtist());
assertEquals(song.getTrackNumber(), Integer.valueOf(1)); assertEquals(song.getTrackNumber(), Integer.valueOf(1));