В этой статье рассмотрим создание своего простого ORM на примере работы с СУБД Oracle.
Создадим интерфейс Persistence и реализующий его класс PersistenceOraImpl - собственно сам персистенс Api для работы с БД.
Список методов в Persistence:
1. selectAll - выбирает все записи из таблицы;
2. select - выбирает записи по заданным параметрам;
3. selectRow - выбирает одну запись по заданным параметрам;
4. insert - вставляет запись;
5. update - обновляет запись;
6. delete - удаляет запись.
Сам интерфейс выглядит так:
public interface Persistence { public List selectAll(Class<?> clazz); public List select(Class<?> clazz, Map<String, Object> params); public Object selectRow(Class<?> clazz, Map<String, Object> params); public void insert(Object obj); public void update(Object obj); public void delete(Class<?> clazz, Map<String, Object> params); public Connection getConnection(); public void setConnection(Connection conn); }
Имплементим методы в классе PersistenceOraImpl.
selectAll:
@Override @Override public List selectAll(Class<?> clazz) { try { OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( "select t.* from " + PersistenceHelper.defineTable(clazz) + " t"); OracleResultSet orset = (OracleResultSet) stmt.executeQuery(); List treeList = new ArrayList(); while (orset.next()) { Constructor constructor = clazz.getConstructor(null); Object somethingInstance = constructor.newInstance(null); Field[] fields = clazz.getDeclaredFields(); Map<Integer, Class<?>> mapFields = PersistenceHelper.defineFields(clazz); /*выполняем методы set...()*/ for (Integer i = 1; i <= mapFields.size(); i++) { String s = fields[i - 1].getName(); s = s.substring(0, 1).toUpperCase() + s.substring(1, s.length()); Method method = clazz.getMethod("set" + s, mapFields.get(i)); method.invoke(somethingInstance, PersistenceHelper.getSqlValue(orset, mapFields.get(i), i)); } treeList.add(somethingInstance); } orset.close(); stmt.close(); return treeList; } catch (Exception ex) { ex.printStackTrace(); return nullArray; } }select:
@Override public List select(Class<?> clazz, Map<String, Object> params) { try { String sSql = "select t.* from " + PersistenceHelper.defineTable(clazz) + " t "; String sSql1 = "where "; Integer j = 1; for (String key : params.keySet()) { if (key.equals("period")) { sSql1 += " to_char(t.period,'MM.yyyy') = to_char(?,'MM.yyyy') "; } else if (key.substring(key.length() - 1).equals(">")) { sSql1 += key + "= ?"; } else if (key.substring(key.length() - 1).equals("<")) { sSql1 += key + "= ?"; } else { sSql1 += key + " = ?"; } if (j++ < params.size()) { sSql1 += " and "; } } OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( sSql + sSql1); System.out.println("select: " + sSql + sSql1); j = 1; for (String key : params.keySet()) { PersistenceHelper.setSqlValue(stmt, params.get(key).getClass(), j++, params.get(key)); } OracleResultSet orset = (OracleResultSet) stmt.executeQuery(); List treeList = new ArrayList(); while (orset.next()) { Constructor constructor = clazz.getConstructor(null); Object somethingInstance = constructor.newInstance(null); Field[] fields = clazz.getDeclaredFields(); Map<Integer, Class<?>> mapFields = PersistenceHelper.defineFields(clazz); /*выполняем методы set...()*/ for (Integer i = 1; i <= mapFields.size(); i++) { String s = fields[i - 1].getName(); s = s.substring(0, 1).toUpperCase() + s.substring(1, s.length()); Method method = clazz.getMethod("set" + s, mapFields.get(i)); method.invoke(somethingInstance, PersistenceHelper.getSqlValue(orset, mapFields.get(i), i)); } treeList.add(somethingInstance); } orset.close(); stmt.close(); return treeList; } catch (Exception ex) { ex.printStackTrace(); return nullArray; } }selectRow:
@Override public Object selectRow(Class<?> clazz, Map<String, Object> params) { try { String sSql = "select t.* from " + PersistenceHelper.defineTable(clazz) + " t "; String sSql1 = " where "; Integer j = 1; for (String key : params.keySet()) { if (key.equals("period")) { sSql1 += " to_char(t.period,'MM') = to_char(?,'MM') "; } else { sSql1 += key + " = ? "; } if (j++ < params.size()) { sSql1 += " and "; } } OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( sSql + sSql1); System.out.println("selectRow: " + sSql + sSql1); j = 1; for (String key : params.keySet()) { PersistenceHelper.setSqlValue(stmt, params.get(key).getClass(), j++, params.get(key)); } OracleResultSet orset = (OracleResultSet) stmt.executeQuery(); List treeList = new ArrayList(); while (orset.next()) { Constructor constructor = clazz.getConstructor(null); Object somethingInstance = constructor.newInstance(null); Field[] fields = clazz.getDeclaredFields(); Map<Integer, Class<?>> mapFields = PersistenceHelper.defineFields(clazz); /*выполняем методы set...()*/ for (Integer i = 1; i <= mapFields.size(); i++) { String s = fields[i - 1].getName(); s = s.substring(0, 1).toUpperCase() + s.substring(1, s.length()); Method method = clazz.getMethod("set" + s, mapFields.get(i)); method.invoke(somethingInstance, PersistenceHelper.getSqlValue(orset, mapFields.get(i), i)); } treeList.add(somethingInstance); } orset.close(); stmt.close(); if (treeList.size() > 0) { return treeList.get(0); } else { Constructor constructor = clazz.getConstructor(null); return constructor.newInstance(null); } } catch (Exception ex) { ex.printStackTrace(); return new Object(); } }insert:
@Override public void insert(Object obj) { try { Class<?> clazz = obj.getClass(); Map<Integer, Class<?>> mapFields = PersistenceHelper.defineFields(clazz); Map<Integer, String> mapFieldNames = PersistenceHelper.defineFieldNames(clazz); Field[] fields = clazz.getDeclaredFields(); String sSql = "insert into " + PersistenceHelper.defineTable(clazz) + " t "; String sSql1 = "("; String sSql2 = ") values ("; for (int i = 1; i <= mapFieldNames.size(); i++) { if (i < mapFieldNames.size()) { sSql1 += mapFieldNames.get(i) + ","; sSql2 += "?,"; } else { sSql1 += mapFieldNames.get(i); sSql2 += "?"; } } OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( sSql + sSql1 + sSql2 + ")"); /*выполняем методы get...()*/ for (Integer i = 1; i <= mapFields.size(); i++) { String s = fields[i - 1].getName(); s = s.substring(0, 1).toUpperCase() + s.substring(1, s.length()); Method method = clazz.getMethod("get" + s); Object o = method.invoke(obj); PersistenceHelper.setSqlValue(stmt, mapFields.get(i), i, o); } stmt.executeUpdate(); stmt.close(); } catch (Exception ex) { ex.printStackTrace(); } }update:
@Override public void update(Object obj) { try { Class<?> clazz = obj.getClass(); Map<Integer, Class<?>> mapFields = PersistenceHelper.defineFields(clazz); Map<Integer, String> mapFieldNames = PersistenceHelper.defineFieldNames(clazz); Field[] fields = clazz.getDeclaredFields(); String sSql = "update " + PersistenceHelper.defineTable(clazz) + " t set "; String sSql1 = ""; for (int i = 1; i <= mapFieldNames.size(); i++) { if (i < mapFieldNames.size()) { sSql1 += mapFieldNames.get(i) + " = ? , "; } else { sSql1 += mapFieldNames.get(i) + " = ? "; } } /*привяжем условие по первому полю. обычно это id*/ sSql1 += " where " + mapFieldNames.get(1) + " = ?"; OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( sSql + sSql1); /*выполняем методы get...()*/ for (Integer i = 1; i <= mapFields.size(); i++) { String s = fields[i - 1].getName(); s = s.substring(0, 1).toUpperCase() + s.substring(1, s.length()); Method method = clazz.getMethod("get" + s); Object o = method.invoke(obj); PersistenceHelper.setSqlValue(stmt, mapFields.get(i), i, o); /*привяжем переменную where*/ if (i == 1) { PersistenceHelper.setSqlValue(stmt, mapFields.get(i), mapFields.size() + 1, o); } } stmt.executeUpdate(); stmt.close(); } catch (Exception ex) { ex.printStackTrace(); } }delete:
@Override public void delete(Class<?> clazz, Map<String, Object> params) { try { String sSql = "delete from " + PersistenceHelper.defineTable(clazz) + " t "; String sSql1 = " where "; Integer j = 1; for (String key : params.keySet()) { if (key.equals("period")) { sSql1 += " to_char(t.period,'MM') = to_char(?,'MM') "; } else { sSql1 += key + " = ? "; } if (j++ < params.size()) { sSql1 += " and "; } } OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement( sSql + sSql1); j = 1; for (String key : params.keySet()) { PersistenceHelper.setSqlValue(stmt, params.get(key).getClass(), j++, params.get(key)); } stmt.executeUpdate(); stmt.close(); } catch (Exception ex) { ex.printStackTrace(); } }Остальная часть класса:
public class PersistenceOraImpl implements Persistence { private static final List nullArray = new ArrayList(); private OracleConnection conn; public PersistenceOraImpl(OracleConnection conn) { setConnection(conn); } /* методы CRUD ... */ @Override public OracleConnection getConnection() { return conn; } @Override public void setConnection(Connection conn) { this.conn = (OracleConnection) conn; }В методах используется вспомогательный класс PersistenceHelper:
public class PersistenceHelper { /** * Возвращает название таблицы из аннотации * * @param clazz тип передаваемого класса * @return oracle String. */ public static String defineTable(Class clazz) { String table; if (clazz.isAnnotationPresent(OraDataTable.class)) { table = clazz.getAnnotation(OraDataTable.class).value(); } else { throw new IllegalStateException(clazz.getName() + " must be @OraDataTable"); } return table.toUpperCase(); } /** * Возвращает названия и типы полей * * @param clazz тип передаваемого класса * @return oracle MapАннотации для обработки entity с помощью reflection:. */ public static Map<Integer, Class<?>> defineFields(Class<?> clazz) { Map<Integer, Class<?>> map = new HashMap<Integer, Class<?>>(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(OraInteger.class)) { map.put(field.getAnnotation(OraInteger.class).value(), Integer.class); } else if (field.isAnnotationPresent(OraString.class)) { map.put(field.getAnnotation(OraString.class).value(), String.class); } else if (field.isAnnotationPresent(OraDate.class)) { map.put(field.getAnnotation(OraDate.class).value(), Date.class); } else if (field.isAnnotationPresent(OraTimestamp.class)) { map.put(field.getAnnotation(OraTimestamp.class).value(), Timestamp.class); } else if (field.isAnnotationPresent(OraBigDecimal.class)) { map.put(field.getAnnotation(OraBigDecimal.class).value(), BigDecimal.class); } else if (field.isAnnotationPresent(OraClob.class)) { map.put(field.getAnnotation(OraClob.class).value(), Clob.class); } else if (field.isAnnotationPresent(OraBlob.class)) { map.put(field.getAnnotation(OraBlob.class).value(), Blob.class); } } return map; } public static Map<Integer, String> defineFieldNames(Class<?> clazz) { Map<Integer, String> map = new HashMap<Integer, String>(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(OraInteger.class)) { map.put(field.getAnnotation(OraInteger.class).value(), field.getAnnotation(OraInteger.class).field()); } else if (field.isAnnotationPresent(OraString.class)) { map.put(field.getAnnotation(OraString.class).value(), field.getAnnotation(OraString.class).field()); } else if (field.isAnnotationPresent(OraDate.class)) { map.put(field.getAnnotation(OraDate.class).value(), field.getAnnotation(OraDate.class).field()); } else if (field.isAnnotationPresent(OraTimestamp.class)) { map.put(field.getAnnotation(OraTimestamp.class).value(), field.getAnnotation(OraTimestamp.class).field()); } else if (field.isAnnotationPresent(OraBigDecimal.class)) { map.put(field.getAnnotation(OraBigDecimal.class).value(), field.getAnnotation(OraBigDecimal.class).field()); } else if (field.isAnnotationPresent(OraClob.class)) { map.put(field.getAnnotation(OraClob.class).value(), field.getAnnotation(OraClob.class).field()); } else if (field.isAnnotationPresent(OraBlob.class)) { map.put(field.getAnnotation(OraBlob.class).value(), field.getAnnotation(OraBlob.class).field()); } } return map; } public static Object getSqlValue(OracleResultSet orset, Class<?> oraType, Integer i) throws SQLException { if (oraType.getName().equals("java.lang.Integer")) { return orset.getInt(i); } else if (oraType.getName().equals("java.lang.String")) { return orset.getString(i); } else if (oraType.getName().equals("java.sql.Date")) { return orset.getDate(i); } else if (oraType.getName().equals("java.sql.Timestamp")) { return orset.getTimestamp(i); } else if (oraType.getName().equals("java.math.BigDecimal")) { return orset.getBigDecimal(i); } else if (oraType.getName().equals("java.sql.Clob")) { return orset.getClob(i); } else if (oraType.getName().equals("java.sql.Blob")) { return orset.getBlob(i); } else { return null; } } public static void setSqlValue(OraclePreparedStatement stmt, Class<?> oraType, Integer i, Object val) throws SQLException, Exception { if (oraType.getName().equals("java.lang.Integer")) { stmt.setInt(i, (Integer) (val == null ? 0 : val)); } else if (oraType.getName().equals("java.lang.String")) { stmt.setString(i, (String) val); } else if (oraType.getName().equals("java.sql.Date")) { stmt.setDate(i, (Date) val); } else if (oraType.getName().equals("java.sql.Timestamp")) { stmt.setTimestamp(i, (Timestamp) val); } else if (oraType.getName().equals("java.math.BigDecimal")) { stmt.setBigDecimal(i, (BigDecimal) val); } else if (oraType.getName().equals("java.sql.Clob")) { stmt.setClob(i, (Clob) val); } else if (oraType.getName().equals("java.sql.Blob")) { stmt.setBlob(i, (Blob) val); } else { throw new Exception("error in setSqlValue. Unknown type: " + oraType.getName()); } } public static void setStatementValue(OracleCallableStatement ocs, String key, Class<?> oraType, Integer i, Object val) throws SQLException, Exception { if (key.equals("out")) { if (oraType.getName().equals("java.lang.Integer")) { ocs.registerOutParameter(1, OracleTypes.INTEGER); } else if (oraType.getName().equals("java.lang.String")) { ocs.registerOutParameter(1, OracleTypes.VARCHAR); } else if (oraType.getName().equals("java.sql.Date")) { ocs.registerOutParameter(1, OracleTypes.DATE); } else if (oraType.getName().equals("java.sql.Timestamp")) { ocs.registerOutParameter(1, OracleTypes.TIMESTAMP); } else if (oraType.getName().equals("java.math.BigDecimal")) { ocs.registerOutParameter(1, OracleTypes.NUMBER); } else if (oraType.getName().equals("java.sql.Clob")) { ocs.registerOutParameter(1, OracleTypes.CLOB); } else if (oraType.getName().equals("java.sql.Blob")) { ocs.registerOutParameter(1, OracleTypes.BLOB); } else if (oraType.getName().equals("java.sql.Array")) { ocs.registerOutParameter(1, OracleTypes.ARRAY); } else { throw new Exception("error in setStatementValue. Unknown out type: " + oraType.getName()); } } else { if (oraType.getName().equals("java.lang.Integer")) { ocs.setInt(i, (Integer) val); } else if (oraType.getName().equals("java.lang.String")) { ocs.setString(i, (String) val); } else if (oraType.getName().equals("java.sql.Date")) { ocs.setDate(i, (Date) val); } else if (oraType.getName().equals("java.sql.Timestamp")) { ocs.setTimestamp(i, (Timestamp) val); } else if (oraType.getName().equals("java.math.BigDecimal")) { ocs.setBigDecimal(i, (BigDecimal) val); } else if (oraType.getName().equals("java.sql.Clob")) { ocs.setClob(i, (Clob) val); } else if (oraType.getName().equals("java.sql.Blob")) { ocs.setBlob(i, (Blob) val); } else if (oraType.getName().equals("java.sql.Array")) { ocs.setArray(i, (Array) val); } else { throw new Exception("error in setStatementValue. Unknown type: " + oraType.getName()); } } } public static Object getStatementValue(OracleCallableStatement ocs, Class<?> oraType, Integer i) throws SQLException, Exception { if (oraType.getName().equals("java.lang.Integer")) { return ocs.getInt(1); } else if (oraType.getName().equals("java.lang.String")) { return ocs.getString(i); } else if (oraType.getName().equals("java.util.Date")) { return ocs.getDate(1); } else if (oraType.getName().equals("java.sql.Timestamp")) { return ocs.getTimestamp(1); } else if (oraType.getName().equals("java.math.BigDecimal")) { return ocs.getBigDecimal(1); } else if (oraType.getName().equals("java.sql.Clob")) { return ocs.getClob(1); } else if (oraType.getName().equals("java.sql.Blob")) { return ocs.getBlob(1); } else if (oraType.getName().equals("java.sql.Array")) { return ocs.getArray(1); } else { throw new Exception("error in setStatementValue. Unknown out type: " + oraType.getName()); } } }
/** * Аннотация класса. */ @Target(value = ElementType.TYPE) @Retention(value = RetentionPolicy.RUNTIME) public @interface OraDataTable { /** * @return oracle table. */ String value(); } /** * Аннотация аттрибута класса. * для конвертации oracle.sql.Int в объект Integer. */ @Target(value = ElementType.FIELD) @Retention(value = RetentionPolicy.RUNTIME) public @interface OraInteger { /** * @return oracle Integer. */ int value(); String field(); } /** * Аннотация аттрибута класса. * для конвертации oracle.sql.String в объект String. */ @Target(value = ElementType.FIELD) @Retention(value = RetentionPolicy.RUNTIME) public @interface OraString { /** * @return oracle String. */ int value(); String field(); } /** * Аннотация аттрибута класса. * для конвертации oracle.sql.Int в объект Integer. */ @Target(value = ElementType.FIELD) @Retention(value = RetentionPolicy.RUNTIME) public @interface OraBigDecimal { /** * @return oracle Integer. */ int value(); String field(); } /** * Аннотация аттрибута класса. * для конвертации oracle.sql.Int в объект Integer. */ @Target(value = ElementType.FIELD) @Retention(value = RetentionPolicy.RUNTIME) public @interface OraDate { /** * @return oracle Integer. */ int value(); String field(); }С другими типами аннотации аналогичны. Теперь с помощью созданных аннотаций можно создать обьект-сущность. Из определения ORM (Object-relational mapping) следует что это отображение Java-обьектов в реляционный вид.
/** * Обьект-сущность */ @OraDataTable("my_table") public class MyTable { @OraInteger(value = 1, field = "id") private Integer id; @OraString(value = 2, field = "name") private Integer periodType; /* и т.д. */ public String getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } /* и т.д. */ }после этого можно проводить операции с этой сущностью в базе данных:
/*достанем все записи из таблицы my_table*/ public List<MyTable> getAllRecords() { try { OracleConnection conn = (OracleConnection) dataSource.getConnection(); Persistence persistence = new PersistenceOraImpl((OracleConnection) conn); List<MyTable> resultList = persistence.selectAll(CalcPeriod.class); conn.close(); return resultList; } catch (SQLException e) { e.printStackTrace(); return nullArray; } } /*достанем несколько записей из таблицы my_table по условию*/ public List<MyTable> getListRecords(MyTable entity) { try { OracleConnection conn = (OracleConnection) dataSource.getConnection(); Persistence persistence = new PersistenceOraImpl((OracleConnection) conn); Map<String, Object> params = new HashMap<String, Object>(); if (entity.getId() != null) params.put("id", entity.getId()); List<MyTable> resultList = persistence.select(entity.getClass(), params); conn.close(); return resultList; } catch (SQLException e) { e.printStackTrace(); return nullArray; } } /*достанем одну запись из таблицы my_table по условию*/ public MyTable getRecordById(Integer inId) { try { OracleConnection conn = (OracleConnection) dataSource.getConnection(); Persistence persistence = new PersistenceOraImpl((OracleConnection) conn); Map<String, Object> params = new HashMap<String, Object>(); if (inId != null) params.put("id", inId); MyTable result = (MyTable) persistence.selectRow(MyTable.class, params); conn.close(); return result; } catch (SQLException e) { e.printStackTrace(); return null; } } /*создадим запись в таблице my_table*/ public void insertRecord(MyTable entity) { try { OracleConnection conn = (OracleConnection) dataSource.getConnection(); Persistence persistence = new PersistenceOraImpl((OracleConnection) conn); persistence.insert(entity); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } /*обновим запись в таблице my_table*/ public void updateRecord(MyTable entity) { try { OracleConnection conn = (OracleConnection) dataSource.getConnection(); Persistence persistence = new PersistenceOraImpl((OracleConnection) conn); persistence.update(entity); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } /*удалим запись в таблице my_table*/ public void deleteRecord(MyTable entity) { try { OracleConnection conn = (OracleConnection) dataSource.getConnection(); Persistence persistence = new PersistenceOraImpl((OracleConnection) conn); Map<String, Object> params = new HashMap<String, Object>(); if (entity.getId() != null) params.put("id", entity.getId()); persistence.delete(entity.getClass(), params); conn.close(); } catch (SQLException e) { e.printStackTrace(); } }Замечу что в данном примере я беру connection к БД из готового dataSource.
Конечно данный ORM покажется излишне простым, нельзя работать с join-ами, транзакции придется писать самому и еще нельзя много чего другого. Думаю в нашем случае это и не надо.
Ну и напоследок стоит упомянуть, что серьезные ORM, например Hibernate, написаны с использованием FastMethod из cglib, и тем самым работают на порядок быстрее, чем с использованием reflection.
Комментариев нет:
Отправить комментарий