登录 注册

【JAVA】基于注解和反射的简单ORM工具

利用注解和反射进行制作的简单的ORM工具,实现了针对对象的单表插入、修改操作
时间:2016-08-10 15:50:38 作者:Mr.d


        上家公司在程序的持久化层没有使用ORM的框架,用的就是一个基于JDBC封装的一个工具类来进行增删改查工作,ORM有ORM的好处,这种JDBC也有JDBC的好处,不过这玩意有个很不方便的地方就是,如果要对一个表进行插入的时候,需要写一些`insert into table (field,field...) values (val,val...)` 这样的SQL语句,好吧,如果一个表字段多一些,就要写好久,然后查询时候返回的结果集是List<Map<String,Object>>的,如果想转换成对应的JavaBean的话,得手写一行一行的转换,好吧,再如果一个表字段多一些......   虽然这个东西是一次性的书写,写完就放那里就可以用了,但是对于懒人来说,不乐意写啊,虽然我在上家公司的时候没考虑写一个工具类,但是现在我想写了呀~~~~


1、注解

        主要有3个注解,Table(表),Key(主键),Column(列),基本上一个表需要这三个就够了。

        Table注解主要是为了标识该类所对应的数据库表,其中会有一个属性叫做name,name的值为表的名称

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Table {
	String name() default "";
}

        Key注解主要为了标识该字段为表中的主键,该类中没有属性

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Documented
public @interface Key {

}

        Column注解主要为了标识该字段与数据库表中字段的对应,其中会有一个属性叫做name,name的值为表中字段的名称

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Documented
public @interface Column {
	String name() default "";
}

        这里注解类上一共有三个注解,分别为@Retention,@Target,@Documented

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
  作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
  取值(RetentionPoicy)有:
    1.SOURCE:在源文件中有效(即源文件保留)
    2.CLASS:在class文件中有效(即class保留)
    3.RUNTIME:在运行时有效(即运行时保留
    

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
  作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
  取值(ElementType)有:
    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
    
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

        以上内容来自   链接地址


2、实体测试类

        这里简单的写了一个JavaBean,并对其进行了注解的标注,作为测试使用

@Table(name="test_user")
public class TestBean {

	@Key
	@Column(name="user_id")
	private long userId;
	
	@Column(name="user_name")
	private String userName;

	private String sex;
	
	public long getUserId() {
		return userId;
	}

	public void setUserId(long userId) {
		this.userId = userId;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}
	
	
	
}

3、插入和修改代码

        这里应该算是核心代码了,我这里的主要思想就是,根据这个JavaBean里的字段,生成对应的SQL来执行,好吧,我不知道像hibernate,myBatis这样的框架是怎样实现的,反正我是这样实现的。


        通过对象获取其class,并根据class获取该类上的Table注解,可获得该对象对应的表名,再次通过class获取类的Field,并根据field获取字段上的Column注解和Key注解,并进行循环拼接SQL...

        在插入操作时,默认主键都是自增主键,所以扫描到Key注解后,不会将该字段拼接到SQL中,其相应的值也不会放入paramList中。

        在更新操作时,会对字段的值进行判断,如果该对象的值为空,则不会更新该字段,但是Key注解(主键)对应的字段必须有值,因为更新是以主键为条件进行更新的。

       public static <T> long insert(T t){
		long res = -1;
		try {

			String sql = getBeanInsertSql(t);
			if (StringUtil.isEmpty(sql))
				return -2;
					
			
			List<Object> paramList = new ArrayList<Object>();
			for (Field field : t.getClass().getDeclaredFields()) {
				Annotation keyAnn = field.getAnnotation(Key.class);
				if (keyAnn != null) {
					continue;
				}
				
				for (Annotation ann : field.getAnnotations()) {
					if (!ann.annotationType().equals(Column.class)) 
						continue;
					
					field.setAccessible(true); // 设置些属性是可以访问的
					Object val = field.get(t); // 得到此属性的值

					paramList.add(val);
					
				}
			}
			
			//这里进行JDBC操作,直接用PreparedStatement执行, 然后将paramList里的参数设置进去就好
			
		} catch (Exception e) {
			Logs.geterrorLogger().error("insert bean failed : ",e);
			return res;
		}
		
		return res;
	}
	
	
	public static <T> long update(T t){
		
		
		try {
			Table tableAnn = (Table)t.getClass().getAnnotation(Table.class);
			if (tableAnn == null || StringUtil.isEmpty(tableAnn.name())) {
				Logs.geterrorLogger().error("insert bean failed : have no table annotation , or table name is empty");
				return -2;
			}
			
			StringBuilder sqlBuilder = new StringBuilder("update ");
			sqlBuilder.append(tableAnn.name()).append(" set ");
			List<Object> paramList = new ArrayList<Object>();
			
			
			Field keyField = null;
			
			Field[] fields = t.getClass().getDeclaredFields();
			for (Field field : fields) {
				
				for (Annotation ann : field.getAnnotations()) {
					if (ann.annotationType().equals(Column.class)) {
						field.setAccessible(true); // 设置些属性是可以访问的
						Object val = field.get(t); // 得到此属性的值
						if (val == null)
							continue;
						
						if (isFieldEmpty(field.getType(),val)) {
							continue;
						}
						
						Column colAnn = (Column) ann;//获取该属性的注解
						sqlBuilder.append(" `").append(colAnn.name()).append("` = ").append("?,");
						paramList.add(val);
					}else if (ann.annotationType().equals(Key.class)) {
						keyField = field;
						continue;
					}
				}
			}
			
			sqlBuilder.deleteCharAt(sqlBuilder.length()-1);
			
			if (keyField == null) {
				Logs.geterrorLogger().error("update bean failed : this bean no key.");
				return -3;
			}
			
			//获取主键的注解,判断
			Column keyColumn = keyField.getAnnotation(Column.class);
			if (keyColumn == null || StringUtil.isEmpty(keyColumn.name())) {
				Logs.geterrorLogger().error("update bean failed : key field is null or key name is empty");
				return -3;
			}
			
			//获取主键值
			keyField.setAccessible(true);
			Object keyValue = keyField.get(t);
			
			if (keyValue == null) {
				Logs.geterrorLogger().error("update bean failed : key field value is null");
				return -3;
			}
				
			
			sqlBuilder.append(" where `").append(keyColumn.name()).append("` = ?");
			paramList.add(keyValue);
			
			System.out.println(sqlBuilder);
			
			//这里进行JDBC操作,直接用PreparedStatement执行, 然后将paramList里的参数设置进去就好
			
			return Db.executeUpdate(sqlBuilder.toString(), paramList.toArray());
		} catch (Exception e) {
			Logs.geterrorLogger().error("update bean failed : ",e);
			return -1;
		}
		
		
	}
	
	private static <T> String getBeanInsertSql(T t) {
		
		Table tableAnn = (Table)t.getClass().getAnnotation(Table.class);
		if (tableAnn == null || StringUtil.isEmpty(tableAnn.name())) {
			Logs.geterrorLogger().error("insert bean failed : have no table annotation , or table name is empty");
			return null;
		}
		
		StringBuilder sqlBuilder = new StringBuilder("insert into ");
		sqlBuilder.append("`").append(tableAnn.name()).append("`");
		
		StringBuilder fieldBuilder = new StringBuilder("(");
		StringBuilder vBuilder = new StringBuilder("(");
		//获取表字段,拼接sql
		Field[] fields = t.getClass().getDeclaredFields();
		for (Field field : fields) {
			Annotation keyAnn = field.getAnnotation(Key.class);
			if (keyAnn != null) {
				continue;
			}
			for (Annotation ann : field.getAnnotations()) {
				if (!ann.annotationType().equals(Column.class)) 
					continue;
				
				field.setAccessible(true); // 设置些属性是可以访问的
				Column colAnn = (Column) ann;//获取该属性的注解

				fieldBuilder.append("`").append(colAnn.name()).append("`,");
				vBuilder.append("?,");
			}
		}
		
		fieldBuilder.deleteCharAt(fieldBuilder.length()-1).append(")");
		vBuilder.deleteCharAt(vBuilder.length()-1).append(")");
		sqlBuilder.append(" ").append(fieldBuilder).append(" values ").append(vBuilder);
		return sqlBuilder.toString();
	}
	
	private static boolean isFieldEmpty(Class type,Object value) {
		if (type.equals(Integer.class) || type.equals(int.class)){
			return (Integer)value == 0;
		} else if (type.equals(Long.class) || type.equals(long.class)) {
			return (Long)value == 0;
		} else if (type.equals(Double.class) || type.equals(double.class) ) {
			return (Double)value == 0;
		} else if (type.equals(Float.class) || type.equals(float.class)) {
			return (Float)value == 0;
		} else {
			return (value == null);
		}
	}

4、总结

        其实这也不是什么高级的东西,就是没人写,我想起来了,就写一写,不过写的过程中也有点波折,还好写出来了,代码很烂,里面一些StringUtil的工具类其实就是一个对String的操作,比如isEmpty就是判断为空的,可以自己写一下,还有Logs这个就是日志输出,可以改成System.out.println()。。。。

        算是今天也学习了吧,回头把之前写的那个List<Map<String,Object>>工具类也分享出来.... 

        个人见解,不喜勿喷,文中链接作者若有意见请联系我,我会及时删除....

评论


暂无评论