diff options
Diffstat (limited to 'sql-persistence/src/main/java/io')
10 files changed, 863 insertions, 18 deletions
diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java index 1cb8405..ab92994 100644 --- a/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/AbstractTypedQuery.java @@ -19,20 +19,18 @@ import java.util.List; import java.util.Map; import java.util.Set; -public abstract class AbstractTypedQuery<A> implements TypedQuery<A> { +public abstract class AbstractTypedQuery<A> implements TypedQuery<A> , FromResultSet<A> { private final Connection c; - private final SqlEntityDesc sqlEntity; + private final SqlEntityMeta sqlEntity; private int firstResult; private int maxResults; - protected AbstractTypedQuery(Connection c, SqlEntityDesc sqlEntity) { + protected AbstractTypedQuery(Connection c, SqlEntityMeta sqlEntity) { this.c = c; this.sqlEntity = sqlEntity; } - public abstract A fromResultSet(ResultSet rs) throws SQLException; - @Override public TypedQuery<A> setMaxResults(int maxResult) { this.maxResults = maxResult; @@ -192,10 +190,10 @@ public abstract class AbstractTypedQuery<A> implements TypedQuery<A> { public List<A> getResultList(Integer offset, Integer limit) { String sql = "SELECT " + sqlEntity.defaultFields + " FROM " + sqlEntity.tableName; - if(offset != null) { + if (offset != null) { sql += " OFFSET " + offset; } - if(limit != null) { + if (limit != null) { sql += " LIMIT " + limit; } diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/FromResultSet.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/FromResultSet.java new file mode 100644 index 0000000..ec25f50 --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/FromResultSet.java @@ -0,0 +1,8 @@ +package io.trygvis.persistence.sql; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface FromResultSet<T> { + T fromResultSet(ResultSet rs) throws SQLException; +} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlDao.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlDao.java new file mode 100644 index 0000000..7df3658 --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlDao.java @@ -0,0 +1,25 @@ +package io.trygvis.persistence.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +public abstract class SqlDao<Id, T> implements FromResultSet<T> { + + protected final Connection c; + + protected SqlDao(Connection c) { + this.c = c; + } + + public abstract void insert(T o) throws SQLException; + + public abstract void delete(T o) throws SQLException; + + public abstract void deleteById(Id id) throws SQLException; + +// public abstract TypedQuery<T> query(); + + public abstract T selectById(Id id) throws SQLException; + + public abstract void update(T entity) throws SQLException; +} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityDesc.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityDesc.java deleted file mode 100644 index dbbeed7..0000000 --- a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityDesc.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.trygvis.persistence.sql; - -public class SqlEntityDesc { - public final String tableName; - public final String defaultFields; - - public SqlEntityDesc(String tableName, String defaultFields) { - this.tableName = tableName; - this.defaultFields = defaultFields; - } -} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManager.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManager.java new file mode 100644 index 0000000..adcd2e2 --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManager.java @@ -0,0 +1,419 @@ +package io.trygvis.persistence.sql; + +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.PersistenceException; +import javax.persistence.Query; +import javax.persistence.RollbackException; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.metamodel.Metamodel; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static javax.persistence.FlushModeType.AUTO; +import static javax.persistence.LockModeType.NONE; + +public abstract class SqlEntityManager implements EntityManager { + + private Map<String, Object> properties = new HashMap<>(); + + public abstract <Id, T> SqlDao<Id, T> getDao(Class<T> klass); + + // ----------------------------------------------------------------------- + // + // ----------------------------------------------------------------------- + + private final SqlEntityManagerFactory factory; + + private final Connection c; + + private boolean autoCommit; + + private final Tx tx = new Tx(); + + protected Connection currentConnection() { + return c; + } + + // ----------------------------------------------------------------------- + // EntityManager Implementation + // ----------------------------------------------------------------------- + + protected SqlEntityManager(SqlEntityManagerFactory factory, Connection c) { + this.factory = factory; + this.c = c; + + try { + autoCommit = c.getAutoCommit(); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public void persist(Object entity) { + if (entity == null) { + throw new NullPointerException("entity"); + } + + try { + @SuppressWarnings("unchecked") + Class<Object> klass = (Class<Object>) entity.getClass(); + this.<Object, Object>getDao(klass).insert(entity); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public <T> T merge(T entity) { + if (entity == null) { + throw new NullPointerException("entity"); + } + + try { + @SuppressWarnings("unchecked") + Class<T> klass = (Class<T>) entity.getClass(); + SqlDao<Object, T> dao = getDao(klass); + T t = dao.selectById(entity); + if (t == null) { + dao.insert(entity); + } else { + dao.update(entity); + } + + return t; + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public void remove(Object entity) { + if (entity == null) { + throw new NullPointerException("entity"); + } + + try { + @SuppressWarnings("unchecked") + Class<Object> klass = (Class<Object>) entity.getClass(); + this.<Object, Object>getDao(klass).delete(entity); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public <T> T find(Class<T> entityClass, Object primaryKey) { + try { + SqlDao<Object, T> dao = getDao(entityClass); + return dao.selectById(primaryKey); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties) { + return find(entityClass, primaryKey); + } + + @Override + public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode) { + if (lockMode != NONE) { + throw new IllegalArgumentException("Only lockMode = NONE is supported."); + } + return find(entityClass, primaryKey); + } + + @Override + public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties) { + return find(entityClass, primaryKey, lockMode); + } + + @Override + public <T> T getReference(Class<T> entityClass, Object primaryKey) { + throw new UnsupportedOperationException(); + } + + @Override + public void flush() { + } + + @Override + public void setFlushMode(FlushModeType flushMode) { + if (flushMode != AUTO) { + throw new RuntimeException("Flush mode not supported: " + flushMode); + } + } + + @Override + public FlushModeType getFlushMode() { + return AUTO; + } + + @Override + public void lock(Object entity, LockModeType lockMode) { + throw new UnsupportedOperationException(); + } + + @Override + public void lock(Object entity, LockModeType lockMode, Map<String, Object> properties) { + throw new UnsupportedOperationException(); + } + + @Override + public void refresh(Object entity) { + throw new UnsupportedOperationException(); + } + + @Override + public void refresh(Object entity, Map<String, Object> properties) { + throw new UnsupportedOperationException(); + } + + @Override + public void refresh(Object entity, LockModeType lockMode) { + throw new UnsupportedOperationException(); + } + + @Override + public void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + } + + @Override + public void detach(Object entity) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains(Object entity) { + throw new UnsupportedOperationException(); + } + + @Override + public LockModeType getLockMode(Object entity) { + throw new UnsupportedOperationException(); + } + + @Override + public void setProperty(String propertyName, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Map<String, Object> getProperties() { + return properties; + } + + @Override + public Query createQuery(String qlString) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass) { + throw new UnsupportedOperationException(); + } + + @Override + public Query createNamedQuery(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass) { + throw new UnsupportedOperationException(); + } + + @Override + public Query createNativeQuery(String sql) { + throw new UnsupportedOperationException(); + } + + @Override + public Query createNativeQuery(String sqlString, Class resultClass) { + // What happens if the transaction is aborted and this query is executes? Can a Query outlive it's connection? + // Or even EntityManager? Should probably store a reference to the current connection and check that the + // current one is the same when executing. + @SuppressWarnings({"UnnecessaryLocalVariable", "unchecked"}) + Class<Object> klass = resultClass; + + SqlDao<?, Object> dao = this.<Object, Object>getDao(klass); + return new SqlQuery<>(dao, new SqlExecutorDelegate(), sqlString, true); + } + + private class SqlExecutorDelegate implements SqlExecutor { + + // For now an EntityManager wraps a Connection + private Connection getCurrentOrNewConnection() { + return c; + } + + @Override + public int executeUpdate(UpdateCommand command) { + if (!isOpen()) { + throw new PersistenceException("This entity manager is closed."); + } + + Connection c = getCurrentOrNewConnection(); + + try { + return command.run(c); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public <T> List<T> executeQuery(QueryCommand<T> command) { + if (!isOpen()) { + throw new PersistenceException("This entity manager is closed."); + } + + Connection c = getCurrentOrNewConnection(); + + try { + return command.run(c); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + } + + @Override + public Query createNativeQuery(String sqlString, String resultSetMapping) { + throw new UnsupportedOperationException(); + } + + @Override + public void joinTransaction() { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T unwrap(Class<T> cls) { + if(cls == SqlUnit.class) { + return cls.cast(this.factory.sqlUnit); + } + throw new PersistenceException(); + } + + @Override + public Object getDelegate() { + throw new UnsupportedOperationException(); + } + + @Override + public void close() { + } + + @Override + public boolean isOpen() { + throw new UnsupportedOperationException(); + } + + @Override + public EntityTransaction getTransaction() { + return tx; + } + + @Override + public SqlEntityManagerFactory getEntityManagerFactory() { + throw new UnsupportedOperationException(); + } + + @Override + public CriteriaBuilder getCriteriaBuilder() { + throw new UnsupportedOperationException(); + } + + @Override + public Metamodel getMetamodel() { + throw new UnsupportedOperationException(); + } + + private class Tx implements EntityTransaction { + private boolean active; + private boolean rollbackOnly; + + @Override + public void begin() { + try { + if (autoCommit) { + c.setAutoCommit(false); + autoCommit = false; + } + active = true; + rollbackOnly = false; + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public void commit() { + if (rollbackOnly) { + throw new RollbackException("Transaction marked for rollback only."); + } + + if (autoCommit) { + return; + } + + try { + c.commit(); + } catch (SQLException e) { + throw new PersistenceException(e); + } + + active = true; + } + + @Override + public void rollback() { + active = false; + if (autoCommit) { + return; + } + + try { + c.rollback(); + } catch (SQLException e) { + throw new PersistenceException(e); + } + } + + @Override + public void setRollbackOnly() { + this.rollbackOnly = active; + } + + @Override + public boolean getRollbackOnly() { + return rollbackOnly; + } + + @Override + public boolean isActive() { + return active; + } + } +} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManagerFactory.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManagerFactory.java new file mode 100644 index 0000000..d48f7e5 --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityManagerFactory.java @@ -0,0 +1,105 @@ +package io.trygvis.persistence.sql; + +import javax.persistence.Cache; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.PersistenceException; +import javax.persistence.PersistenceUnitUtil; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.metamodel.Metamodel; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; + +import static java.util.Collections.emptyMap; + +public abstract class SqlEntityManagerFactory implements EntityManagerFactory { + + public final SqlUnit sqlUnit; + + private final DataSource dataSource; + + protected SqlEntityManagerFactory(SqlUnit sqlUnit, DataSource dataSource) { + this.sqlUnit = sqlUnit; + this.dataSource = dataSource; + } + + // ----------------------------------------------------------------------- + // + // ----------------------------------------------------------------------- + + protected abstract EntityManager createEntityManager(Connection c); + + // ----------------------------------------------------------------------- + // + // ----------------------------------------------------------------------- + + @Override + public EntityManager createEntityManager() { + return createEntityManager(emptyMap()); + } + + @Override + public EntityManager createEntityManager(Map map) { + try { + Connection c = dataSource.getConnection(); + return createEntityManager(c); + } catch (SQLException e) { + throw new PersistenceException("Could not get connection", e); + } + } + + @Override + public CriteriaBuilder getCriteriaBuilder() { + throw new UnsupportedOperationException(); + } + + @Override + public Metamodel getMetamodel() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public void close() { + } + + @Override + public Map<String, Object> getProperties() { + return emptyMap(); + } + + @Override + public Cache getCache() { + return new NoOpCache(); + } + + @Override + public PersistenceUnitUtil getPersistenceUnitUtil() { + throw new UnsupportedOperationException(); + } + + private static class NoOpCache implements Cache { + @Override + public boolean contains(Class cls, Object primaryKey) { + return false; + } + + @Override + public void evict(Class cls, Object primaryKey) { + } + + @Override + public void evict(Class cls) { + } + + @Override + public void evictAll() { + } + } +} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityMeta.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityMeta.java new file mode 100644 index 0000000..c8cbcac --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlEntityMeta.java @@ -0,0 +1,15 @@ +package io.trygvis.persistence.sql; + +public class SqlEntityMeta { + public final String tableName; + public final String defaultFields; + public final String createTableSql; + public final String dropTableSql; + + public SqlEntityMeta(String tableName, String defaultFields, String createTableSql, String dropTableSql) { + this.tableName = tableName; + this.defaultFields = defaultFields; + this.createTableSql = createTableSql; + this.dropTableSql = dropTableSql; + } +} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlExecutor.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlExecutor.java new file mode 100644 index 0000000..ad6b8af --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlExecutor.java @@ -0,0 +1,19 @@ +package io.trygvis.persistence.sql; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +public interface SqlExecutor { + int executeUpdate(UpdateCommand command); + + <T> List<T> executeQuery(QueryCommand<T> command); + + static interface UpdateCommand { + int run(Connection c) throws SQLException; + } + + static interface QueryCommand<T> { + List<T> run(Connection c) throws SQLException; + } +} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlQuery.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlQuery.java new file mode 100644 index 0000000..b50b56e --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlQuery.java @@ -0,0 +1,249 @@ +package io.trygvis.persistence.sql; + +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.NoResultException; +import javax.persistence.NonUniqueResultException; +import javax.persistence.Parameter; +import javax.persistence.PersistenceException; +import javax.persistence.TemporalType; +import javax.persistence.TypedQuery; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.emptyMap; + +public class SqlQuery<T> implements TypedQuery<T> { + private final FromResultSet<T> fromResultSet; + private final SqlExecutor executor; + private final String sql; + + private final boolean isSqlFinal; + private int maxResults = -1; + private int firstResult = -1; + + public SqlQuery(FromResultSet<T> fromResultSet, SqlExecutor executor, String sql, boolean sqlFinal) { + this.fromResultSet = fromResultSet; + this.executor = executor; + this.sql = sql; + this.isSqlFinal = sqlFinal; + } + + @Override + public List<T> getResultList() { + return getResultList(firstResult, maxResults); + } + + @Override + public T getSingleResult() { + List<T> list = getResultList(0, 2); + if (list.size() == 1) { + return list.get(0); + } + + if (list.size() == 0) { + throw new NoResultException(); + } + + throw new NonUniqueResultException(); + } + + public List<T> getResultList(int offset, int limit) { + final String sql = generateSql(this.sql, offset, limit); + return executor.executeQuery(new SqlExecutor.QueryCommand<T>() { + @Override + public List<T> run(Connection c) throws SQLException { + List<T> list = new ArrayList<>(); + try (Statement stmt = c.createStatement()) { + ResultSet rs = stmt.executeQuery(sql); + while (rs.next()) { + T t = fromResultSet.fromResultSet(rs); + list.add(t); + } + } + return list; + } + }); + } + + private static String generateSql(String sql, int offset, int limit) { + if (offset > 0) { + sql += " OFFSET " + offset; + } + if (limit > 0) { + sql += " LIMIT " + limit; + } + + return sql; + } + + @Override + public int executeUpdate() { + return executor.executeUpdate(new SqlExecutor.UpdateCommand() { + @Override + public int run(Connection c) throws SQLException { + try (Statement stmt = c.createStatement()) { + return stmt.executeUpdate(sql); + } + } + }); + } + + @Override + public SqlQuery<T> setMaxResults(int maxResult) { + if (maxResult <= 0) { + throw new IllegalArgumentException("maxResult has to be positive: " + maxResult); + } + this.maxResults = maxResult; + return this; + } + + @Override + public int getMaxResults() { + return maxResults; + } + + @Override + public SqlQuery<T> setFirstResult(int startPosition) { + this.firstResult = startPosition; + return this; + } + + @Override + public int getFirstResult() { + return firstResult; + } + + @Override + public SqlQuery<T> setHint(String hintName, Object value) { + return null; + } + + @Override + public Map<String, Object> getHints() { + return emptyMap(); + } + + @Override + public <A> SqlQuery<T> setParameter(Parameter<A> param, A value) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(Parameter<Date> param, Date value, TemporalType temporalType) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(String name, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(String name, Calendar value, TemporalType temporalType) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(String name, Date value, TemporalType temporalType) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(int position, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(int position, Calendar value, TemporalType temporalType) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setParameter(int position, Date value, TemporalType temporalType) { + throw new UnsupportedOperationException(); + } + + @Override + public Set<Parameter<?>> getParameters() { + throw new UnsupportedOperationException(); + } + + @Override + public Parameter<?> getParameter(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Parameter<T> getParameter(String name, Class<T> type) { + throw new UnsupportedOperationException(); + } + + @Override + public Parameter<?> getParameter(int position) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Parameter<T> getParameter(int position, Class<T> type) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isBound(Parameter<?> param) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T getParameterValue(Parameter<T> param) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getParameterValue(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public Object getParameterValue(int position) { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setFlushMode(FlushModeType flushMode) { + throw new UnsupportedOperationException(); + } + + @Override + public FlushModeType getFlushMode() { + throw new UnsupportedOperationException(); + } + + @Override + public SqlQuery<T> setLockMode(LockModeType lockMode) { + throw new UnsupportedOperationException(); + } + + @Override + public LockModeType getLockMode() { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T unwrap(Class<T> klass) { + throw new PersistenceException("Unsupported class: " + klass); + } +} diff --git a/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlUnit.java b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlUnit.java new file mode 100644 index 0000000..2879f5d --- /dev/null +++ b/sql-persistence/src/main/java/io/trygvis/persistence/sql/SqlUnit.java @@ -0,0 +1,18 @@ +package io.trygvis.persistence.sql; + +import java.util.List; + +import static java.util.Arrays.asList; + +public class SqlUnit { + + private final List<SqlEntityMeta> entities; + + public SqlUnit(SqlEntityMeta... entities) { + this.entities = asList(entities); + } + + public List<SqlEntityMeta> getEntities() { + return entities; + } +} |