zoukankan      html  css  js  c++  java
  • java基础复习-自定义注解4(结合JDBC技术,打造类表映射微框架)

    写在前面:

    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等框架中,是否会着更多的反射技术的使用?而这些框架给我们带来快捷开发的同时,是否也在一定程度上影响了系统的性能呢?

  • 相关阅读:
    Ubuntu ctrl+alt会导致窗口还原的问题
    Ubuntu设置显示桌面快捷键
    ubuntu鼠标和触摸板的禁用
    Codeforces Round #271 (Div. 2) F题 Ant colony(线段树)
    友盟社会化分享
    ZOJ 3890 Wumpus
    九种迹象表明你该用Linux了
    Java集合源代码剖析(二)【HashMap、Hashtable】
    01_GIT基础、安装
    Launcher知识的demo,手机管家小伙家与悬浮窗
  • 原文地址:https://www.cnblogs.com/xgp123/p/12275882.html
Copyright © 2011-2022 走看看