Tea Framework-Implementation of ORM Framework (I)

Posted by treybraid on Tue, 04 Jun 2019 05:23:30 +0200

I. Achieving Requirements

1. The responsibility of the data access layer is to add, delete and modify the database, so it can be very single. It only needs one inteface to do it.

2. Automated ORM is not conducive to the optimization of SQL and the customization of SQL, so TeaFrameWork ORM is ready to be implemented in a semi-automatic way. Developers need to write SQL.

3. Farewell configuration file, pure comment;

4. Each method of interface needs only one parameter. It can be PO object, MAP, 8 basic data types + String and Date.

5. Dynamic Binding of SQL

6. Support Oracle, Mysql

7. Automatic Paging

8. The placeholder uses #, such as # ID, # name, which is consistent with ibatis.

Two, analysis

1. ORM only needs inteface in design, so the concrete implementation class must be generated by the framework. After comparing many bytecode generation tools, we decided to adopt cglib.

2. There are essentially only two kinds of database operation: reading and writing.

(1) Read the Select statement, return value type: 8 basic data types + String and Date, PO object, List < PO object >, List < Map < String, Object >, here we limit the return type, basically meet the daily development.

(2) Write: insert, update, delete, etc. There is a primary key acquisition problem in insert operation, which can be automatically generated, or can be obtained by writing SQL, such as Oracle's select_users.nextval from dual.

III. Specific Realization

1, annotations

(1), @TeaDao indicates that this inteface is the data access layer, so that the bean container can be scanned at startup

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TeaDao {
	public String value() default "";
}

(2), @SQL binds SQL statements to specific methods, such as @SQL ("select * from users"), public List < User > getAllUser ();

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SQL {
	public String value();
}

Statements with @GetPrimaryKey annotations to generate primary keys, usually used in conjunction with insert statements

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetPrimaryKey {
	public String sql();

	public String primaryKeyProperty();
}

Such as:

    @GetPrimaryKey(sql = "select s_users.nextval from dual", primaryKeyProperty = "id")
	@SQL("insert into users(id,name,password,createdate) values(#id#,#name#,#password#,#createdate#)")
	public int add(Map<String, Object> map);

(4), @AutoIncrement annotations automatically generate primary keys from the database when they are added, and they are used in conjunction with the new annotations.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoIncrement {

}

(5), @DynamicSQL annotation method's SQL is dynamically imported and used in query scenarios

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DynamicSQL {

}

2. Agent class OrmProxy, which specifically intercepts inteface and executes SQL

public class OrmProxy implements InterfaceExecutor {
	private final static String SELECT = "select";
	private final static String INSERT = "insert";

	private static OrmProxy instance;

	private OrmProxy() {
	}

	public synchronized static OrmProxy getInstance() {
		if (instance == null) {
			instance = new OrmProxy();
		}
		return instance;
	}

	@Override
	public Object invoke(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		if (method.getDeclaringClass().equals(java.lang.Object.class)) {
			return proxy.invokeSuper(obj, args);
		}
		if (!method.isAnnotationPresent(SQL.class) && !method.isAnnotationPresent(DynamicSQL.class)) {
			throw new TeaOrmException("there are no bindings SQL");
		}
		if (args != null && args.length > 1) {
			throw new TeaOrmException("Only one parameter can be passed");
		}
		if (method.isAnnotationPresent(GetPrimaryKey.class) && method.isAnnotationPresent(AutoIncrement.class)) {
			throw new TeaOrmException("GetPrimaryKey and AutoIncrement You can't annotate a method at the same time.");
		}
		if (method.getAnnotation(SQL.class) != null && method.getAnnotation(DynamicSQL.class) != null) {
			throw new TeaOrmException("SQL and DynamicSQL You can't annotate a method at the same time.");
		}
		if (TranscationThreadVariable.get() == null || !TranscationThreadVariable.get()) {
			if (ConnectionThreadVariable.getConnetion() == null) {
				ConnectionThreadVariable.setConnetion(DataSourceHelp.getConnection());
			}
		}
		try {
			if (method.isAnnotationPresent(SQL.class)) {
				String sql = method.getAnnotation(SQL.class).value().trim();
				AbstractDataBind dataBind = DataBindFactory
						.getDataBind(args == null || args.length == 0 ? null : args[0]);
				if (!SELECT.equalsIgnoreCase(sql.substring(0, 6))) {
					boolean isAutoIncrement = false;
					if (INSERT.equalsIgnoreCase(sql.substring(0, 6))) {
						if (method.getAnnotation(AutoIncrement.class) != null) {
							isAutoIncrement = true;
						}
						if (method.getAnnotation(GetPrimaryKey.class) != null) {
							Object object = dataBind.fillPrimaryKey(method, args[0]);// Fill the primary key
							dataBind.excuteUpdate(sql, args == null || args.length == 0 ? null : args[0],
									isAutoIncrement);
							return object;
						}
					}
					return dataBind.excuteUpdate(sql, args == null || args.length == 0 ? null : args[0],
							isAutoIncrement);
				} else {
					return QueryResultProcesser.createQueryResult(
							dataBind.excuteQuery(sql, args == null || args.length == 0 ? null : args[0]), method);
				}
			} else if (method.isAnnotationPresent(DynamicSQL.class)) {
				String sql = DynamicSqlUtil.get() == null ? null : DynamicSqlUtil.get().trim();
				if (null == sql || "".equals(sql)) {
					throw new TeaOrmException("SQL Statement cannot be empty");
				}
				if (sql.length() < 6 || !SELECT.equalsIgnoreCase(sql.substring(0, 6))) {
					throw new TeaOrmException("Bind only select Sentence");
				}
				return QueryResultProcesser.createQueryResult(DataBindFactory.getDataBind(null).excuteQuery(sql,
						args == null || args.length == 0 ? null : args[0]), method);
			}
		} catch (Exception e) {
			throw new TeaOrmException(e);
		} finally {
			if (TranscationThreadVariable.get() == null || !TranscationThreadVariable.get()) {
				ConnectionThreadVariable.getConnetion().close();
				ConnectionThreadVariable.clearThreadVariable();
			}
		}
		return null;
	}

}

Code Interpretation:

(1) Used for all method execution, no shared variables, so there is no thread security problem, so here we use the singleton mode.

(2) Getting SQL is divided into two parts: dynamic SQL and fixed annotated SQL. Dynamic SQL is only used for query scenarios. For additions, you need to get the primary key generation scheme (sql generation and automatic generation)

(3) Interface Executor interface will be described in detail in Bean container design.

(4) Transaction-related code, which will be discussed in detail in transaction design

Since then, an ORM core code has been completed, leaving the execution process of SQL, placeholder replacement. Please pay attention to Tea Framework - Implementation of ORM Framework (2)

 

Project address: https://git.oschina.net/lxkm/teaframework
Blog: https://my.oschina.net/u/1778239/blog 

Topics: SQL Database Oracle MySQL