В этой статье рассмотрим создание своего простого 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.
*/
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());
}
}
}
Аннотации для обработки entity с помощью reflection:
/**
* Аннотация класса.
*/
@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.
Комментариев нет:
Отправить комментарий