写在前面:
1、该框架为自己所写的第一个框架类产品,可能有着许多不足的地方,读者可以到评论区指出。同时,该微框架的源码也会开源至博客中,够后来的学习者借鉴。由于该框架逻辑结构稍些复杂,不可能花大量篇幅去逐一行代码的讲解,本文后半部分会贴出框架源代码,源代码中有着大量的注释,学习者若有看不懂的地方可以在博客后面留言。
2、该框架称之为微框架,在于其实现的功能简单,帮助开发者自动的建库建表,拜托对数据库操作的烦恼。众所周知,在javaee流行框架中mybatis的逆向工程生成的通用Mapper可以让开发者拜托对数据库增删改查语句的编写,but,开发者还需要自己对数据库进行建库建表,因此,也要求开发者掌握相应的数据库知识。使用本框架,框架将自动的为开发者建库建表,配合mybatis框架的通用mapper将实现开发者全程无需自己对数据库直接操作的开发体验。
3、开源地址 https://gitee.com/xue-guo-peng/MyOrm
1、框架的使用
1.1、导入框架jar包,并导入jdbc驱动,添加为项目库。
框架jar包下载地址:https://gitee.com/xue-guo-peng/MyOrm/releases
1.2、导入后的框架目录结构为:
1.3、将数据源的连接信息放到写到一个database.properties文件中,并放在src目录下:
其中的数据库的名字(如图上的aaa)可以填以存在的数据库,也可以填不存在的数据库,如果所填的数据库不存在,将会自动创建。
1.4、编写实体类,并在你需要生成表的实体类上标注注解
在Student类上,我们不标注注解,到时候框架扫描器就会排除该实体类。
在User类上,我们给表和字段标注注解,该实体类用于对数据表的生成:
package com.xgp.company.model; import com.xgp.company.annotaion.MyField; import com.xgp.company.annotaion.MyTable; import com.xgp.company.annotaion.Plan; /** * 用户表 */ @MyTable public class User { //用户ID @MyField(plan = Plan.PK_AUTO,Comment = "这是用户id") private int id; //用户名 @MyField(plan = Plan.NOT_NULL,len = 16,Comment = "这是用户名") private String username; //用户密码 @MyField(plan = Plan.NOT_NULL,len = 32,Comment = "这是用户密码") private String password; //用户性别 1男 0女 @MyField(Comment = "用户性别") private int sex = 1; //性别默认为1 //用户年龄 @MyField(Comment = "用户年龄") private int age = 20; //年龄默认为20 //用户是否被删除 @MyField(Comment = "用户的置废标识") private int is_del; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getIs_del() { return is_del; } public void setIs_del(int is_del) { this.is_del = is_del; } }
说明:set方法不是不需的,get方法是必须的,因为在获取字段的默认值时是通过get方法反射获取的。一个实体类被框架的注解标示,框架会自动的获取类名、属性名、属性类型、以及=后面的放回值,结合注解上的信息,进行表的生成。
1.5、@MyTable注解的说明:
该注解作用于类上,只有一个value属性,可填可不填。填了就是生成数据表的表名,不填则会获取被标注的实体类类名作为表名。
1.6、@MyField注解的说明:
该注解作用于实体类的字段上
name属性为数据表的字段名字,可填可不填,不填则获取实体类的属性名字作为字段名。
len属性为字段的长度,默认为3
plan为字段的相关信息,默认能为NULL
commen为对字段的解释说明
1.6、Plan枚举类的说明:
说明见注释,已经很详细了。
1.7、创建main方法,调用框架
其中需要传递的参数为实体类所在的包名,根据所传的参数包名,框架的注解扫描器将会自动的扫描该包下被标注了@MyTable注解的所有实体类,并根据相应的信息,在数据库中建库建表。
1.8、点击运行,建库建表
如图,则库表创建成功,下面就对解析注解的三个工具类进行简要说明:
2、CreateTable类的说明
该类为反射获取类和注解上的信息工具类,拼接建表的sql语句。
create()函数用于创建表
flag()函数用于判断该实体类是否需要映射到数据库中
init()函数用于拼接建表的sql语句
getFilelds()函数用于反射获取实体类的注解信息和字段信息
getTableName()函数用于获取表的名字
该类代码如下:
package com.xgp.company.tool; import com.xgp.company.annotaion.MyField; import com.xgp.company.annotaion.MyTable; import com.xgp.company.annotaion.Plan; import com.xgp.company.model.User; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @SuppressWarnings("all") public class CreateTable { //获取数据库连接对象 private static Connection con = JDBCUtilsConfig.getConnection(); public static void create(String pack) { List<String> lists = ClazzUtils.getClazzName("com.xgp.company.model", true); for (String list : lists) { String newpack = pack + list.substring(list.lastIndexOf('.')); // System.out.println(newpack); Class<?> clazz = null; try { clazz = Class.forName(newpack); } catch (ClassNotFoundException e) { e.printStackTrace(); } if(flag(clazz)) init(clazz); } } private static boolean flag(Class clazz) { //通过反射获取MyTable注解 //获得注解的value的值 MyTable table = (MyTable) clazz.getAnnotation(MyTable.class); if(table == null) return false; return true; } private static void init(Class clazz) { // Class<User> clazz = User.class; String tableName = getTableName(clazz); List<Map<String, String>> filelds = null; filelds = getFilelds(clazz); /* for (Map<String, String> fileld : filelds) { for (String s : fileld.keySet()) { System.out.println(s + " === " + fileld.get(s)); } }*/ // 拼接sql语句 String sqlTitle = "CREATE TABLE IF NOT EXISTS " + tableName + "("; String sql = sqlTitle; for (Map<String, String> fileld : filelds) { String def = fileld.get("def"); if("VARCHAR".equals(fileld.get("type"))) { def = "DEFAULT" + " " +"'" + def + "'"; }else if("PRIMARY KEY AUTO_INCREMENT".equals(fileld.get("plan")) || "PRIMARY KEY".equals(fileld.get("plan"))) { def = ""; }else { def = "DEFAULT" + " " + def; } String sqlBody = fileld.get("name") + " " + fileld.get("type") + "(" + fileld.get("len") + ")" + " " + fileld.get("plan") + " " + def + " " + "COMMENT" + " '" + fileld.get("comment") + "',"; sql = sql + sqlBody; } sql = sql.substring(0,sql.length() - 1) + ")ENGINE=INNODB DEFAULT CHARSET = 'utf8'"; // System.out.println(sql); //执行sql语句 Statement stmt = null; try { stmt = con.createStatement(); } catch (SQLException e) { e.printStackTrace(); } try { stmt.executeUpdate(sql); } catch (SQLException e) { e.printStackTrace(); } try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } System.out.println(tableName + "数据表创建成功"); } private static List<Map<String,String>> getFilelds(Class clazz){ //获取类上的字段 Field[] fields = clazz.getDeclaredFields(); List<Map<String,String>> list = new ArrayList(); for (Field field : fields) { MyField ano = field.getAnnotation(MyField.class); Map<String,String> map = new HashMap<>(); String name = ano.name(); // 字段名称 if(name.isEmpty()) name = field.getName(); map.put("name",name); // 存字段名称 int len = ano.len(); // 长度 map.put("len",len + "");//存字段长度 Plan plan = ano.plan(); //计划方案 //分析计划 switch (plan) { case PK: map.put("plan","PRIMARY KEY");break; case PK_AUTO:map.put("plan","PRIMARY KEY AUTO_INCREMENT");break; case NOT_NULL:map.put("plan","NOT NULL");break; case NULL:map.put("plan","NULL");break; } String comment = ano.Comment();//字段备注 map.put("comment",comment); //存备注 //获取字段类型 String type = field.getGenericType().getTypeName(); if ("int".equals(type)) { map.put("type","INT"); }else if("java.lang.String".equals(type)) { map.put("type","VARCHAR"); } //获取默认值 Object user = null; try { user = clazz.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } Method getxxx = null; try { getxxx = clazz.getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1)); } catch (NoSuchMethodException e) { e.printStackTrace(); } Object res = null; try { res = getxxx.invoke(user); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } String def = ""; if(res != null) def = res + ""; // System.out.println(res); map.put("def",def); //存入外部集合 list.add(map); } return list; } private static String getTableName(Class clazz) { //通过反射获取MyTable注解 //获得注解的value的值 MyTable table = (MyTable) clazz.getAnnotation(MyTable.class); String tableName = table.value(); //创建表 if(tableName .isEmpty()) //获取类类名,用户没有自己定义就默认为表名 tableName = clazz.getSimpleName(); return tableName; } }
3、ClazzUtils该类用于拼接实体类所在包的.class文件的绝对物理路径,方便于框架的注解扫描器进行扫描
package com.xgp.company.tool; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class ClazzUtils { private static final String CLASS_SUFFIX = ".class"; private static final String CLASS_FILE_PREFIX = File.separator + "classes" + File.separator; private static final String PACKAGE_SEPARATOR = "."; /** 26 * 查找包下的所有类的名字 27 * @param packageName 28 * @param showChildPackageFlag 是否需要显示子包内容 29 * @return List集合,内容为类的全名 30 */ public static List<String> getClazzName(String packageName, boolean showChildPackageFlag ) { List<String> result = new ArrayList<>(); String suffixPath = packageName.replaceAll("\\.", "/"); ClassLoader loader = Thread.currentThread().getContextClassLoader(); try { Enumeration<URL> urls = loader.getResources(suffixPath); while(urls.hasMoreElements()) { URL url = urls.nextElement(); if(url != null) { String protocol = url.getProtocol(); if("file".equals(protocol)) { String path = url.getPath(); System.out.println(path); result.addAll(getAllClassNameByFile(new File(path), showChildPackageFlag)); } else if("jar".equals(protocol)) { JarFile jarFile = null; try{ jarFile = ((JarURLConnection) url.openConnection()).getJarFile(); } catch(Exception e){ e.printStackTrace(); } if(jarFile != null) { result.addAll(getAllClassNameByJar(jarFile, packageName, showChildPackageFlag)); } } } } } catch (IOException e) { e.printStackTrace(); } return result; } private static List<String> getAllClassNameByFile(File file, boolean flag) { List<String> result = new ArrayList<>(); if(!file.exists()) { return result; } if(file.isFile()) { String path = file.getPath(); // 注意:这里替换文件分割符要用replace。因为replaceAll里面的参数是正则表达式,而windows环境中File.separator="\\"的,因此会有问题 if(path.endsWith(CLASS_SUFFIX)) { path = path.replace(CLASS_SUFFIX, ""); // 从"/classes/"后面开始截取 String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length()) .replace(File.separator, PACKAGE_SEPARATOR); if(-1 == clazzName.indexOf("$")) { result.add(clazzName); } } return result; } else { File[] listFiles = file.listFiles(); if(listFiles != null && listFiles.length > 0) { for (File f : listFiles) { if(flag) { result.addAll(getAllClassNameByFile(f, flag)); } else { if(f.isFile()){ String path = f.getPath(); if(path.endsWith(CLASS_SUFFIX)) { path = path.replace(CLASS_SUFFIX, ""); // 从"/classes/"后面开始截取 String clazzName = path.substring(path.indexOf(CLASS_FILE_PREFIX) + CLASS_FILE_PREFIX.length()) .replace(File.separator, PACKAGE_SEPARATOR); if(-1 == clazzName.indexOf("$")) { } } } } } } return result; } } private static List<String> getAllClassNameByJar(JarFile jarFile, String packageName, boolean flag) { List<String> result = new ArrayList<>(); Enumeration<JarEntry> entries = jarFile.entries(); while(entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); String name = jarEntry.getName(); // 判断是不是class文件 if(name.endsWith(CLASS_SUFFIX)) { name = name.replace(CLASS_SUFFIX, "").replace("/", "."); if(flag) { // 如果要子包的文件,那么就只要开头相同且不是内部类就ok if(name.startsWith(packageName) && -1 == name.indexOf("$")) { result.add(name); } } else { // 如果不要子包的文件,那么就必须保证最后一个"."之前的字符串和包名一样且不是内部类 if(packageName.equals(name.substring(0, name.lastIndexOf("."))) && -1 == name.indexOf("$")) { result.add(name); } } } } return result; } public static void main(String[] args) { List<String> list = ClazzUtils.getClazzName("com.xgp.company.model", true); for (String string : list) { System.out.println(string); } } }
4、JDBCUtilsConfig该类用于读取配置文件,获得与数据库的连接,并完成建库操作
package com.xgp.company.tool; /** * JDBC工具类,读取配置文件 */ import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class JDBCUtilsConfig { //定义的连接对象 private static Connection con; //驱动 private static final String dirverClass = "com.mysql.jdbc.Driver"; //数据库的地址 private static String url; //数据库的用户名 private static String username; //数据库的密码 private static String password; //静态代码块读取配置文件 static { //读取配置文件 readConfig(); try { Class.forName(dirverClass); } catch (ClassNotFoundException e) { e.printStackTrace(); } try { String newUrl = url.substring(0,url.lastIndexOf('/')) + "/"; // System.out.println(newUrl); con = DriverManager.getConnection(newUrl,username,password); //创建数据库 Statement stmt = con.createStatement(); int end = url.indexOf('&'); String data = null; if(end == -1) { data = url.substring(url.lastIndexOf('/') + 1); }else { data = url.substring(url.lastIndexOf('/') + 1,end); } String sql = "CREATE DATABASE IF NOT EXISTS " + data +" DEFAULT CHARACTER SET = 'utf8' "; stmt.executeUpdate(sql); stmt.close(); System.out.println(data + "数据库创建成功"); //重新获取连接 con = DriverManager.getConnection(url,username,password); } catch (SQLException e) { e.printStackTrace(); } } private static void readConfig() { //使用反射技术获取文件的输入流 InputStream in = JDBCUtilsConfig.class.getClassLoader().getResourceAsStream("database.properties"); //解析properties的类 Properties pro = new Properties(); try { //读流 pro.load(in); } catch (IOException e) { e.printStackTrace(); } //获取值 username = pro.getProperty("username"); url = pro.getProperty("url"); password = pro.getProperty("password"); } //返回连接对象 public static Connection getConnection() { return con; } //测试是否连接数据库成功 public static void main(String[] args) { Connection con = JDBCUtilsConfig.getConnection(); System.out.println(con); } }
5、思考
通过本次对类表映射微框架的编写,体会到反射技术的重要性。本框架中,采用了大量的反射技术,然后反射技术是会严重影响性能的。因此,在后期web开发中的Spring、mybatis、SpringMvc等框架中,是否会着更多的反射技术的使用?而这些框架给我们带来快捷开发的同时,是否也在一定程度上影响了系统的性能呢?
来源:https://www.cnblogs.com/xgp123/p/12275882.html