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等框架中,是否会着更多的反射技术的使用?而这些框架给我们带来快捷开发的同时,是否也在一定程度上影响了系统的性能呢?
java基础复习-自定义注解4(结合JDBC技术,打造类表映射微框架)的更多相关文章
- Java基础笔记 – Annotation注解的介绍和使用 自定义注解
Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 | 被围观 25,969 views+ 1.Anno ...
- Java基础教程:注解
Java基础教程:注解 本篇文章参考的相关资料链接: 维基百科:https://zh.wikipedia.org/wiki/Java%E6%B3%A8%E8%A7%A3 注解基础与高级应用:http: ...
- Java基础复习笔记系列 九 网络编程
Java基础复习笔记系列之 网络编程 学习资料参考: 1.http://www.icoolxue.com/ 2. 1.网络编程的基础概念. TCP/IP协议:Socket编程:IP地址. 中国和美国之 ...
- Java基础复习笔记系列 八 多线程编程
Java基础复习笔记系列之 多线程编程 参考地址: http://blog.csdn.net/xuweilinjijis/article/details/8878649 今天的故事,让我们从上面这个图 ...
- Java基础复习笔记系列 七 IO操作
Java基础复习笔记系列之 IO操作 我们说的出入,都是站在程序的角度来说的.FileInputStream是读入数据.?????? 1.流是什么东西? 这章的理解的关键是:形象思维.一个管道插入了一 ...
- Java基础复习笔记系列 五 常用类
Java基础复习笔记系列之 常用类 1.String类介绍. 首先看类所属的包:java.lang.String类. 再看它的构造方法: 2. String s1 = “hello”: String ...
- Java基础复习笔记系列 四 数组
Java基础复习笔记系列之 数组 1.数组初步介绍? Java中的数组是引用类型,不可以直接分配在栈上.不同于C(在Java中,除了基础数据类型外,所有的类型都是引用类型.) Java中的数组在申明时 ...
- Java基础复习笔记基本排序算法
Java基础复习笔记基本排序算法 1. 排序 排序是一个历来都是很多算法家热衷的领域,到现在还有很多数学家兼计算机专家还在研究.而排序是计算机程序开发中常用的一种操作.为何需要排序呢.我们在所有的系统 ...
- 《Java基础复习》-控制执行流程
最近任务太多了,肝哭我了,boom 参考书目:Thinking in Java <Java基础复习>-控制执行流程 Java使用了C的所有流程控制语句 涉及关键字:if-else.whil ...
随机推荐
- UVa 400 Unix Is命令
简单题 #include <bits/stdc++.h> using namespace std; const int maxn=110; string s[maxn]; int main ...
- js把树形数据转成扁平数据
我就直接上代码了都是实际项目里面用到的 1.假设这个json就已经是树型结构数据了(如果不知道怎么实现树型结构数据请看我另一篇博客) var compressedArr=afcommon.treeDa ...
- C语言:使用递归解决汉诺塔问题。
//汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小 ...
- python中os.path.abspath与os.path.realpath 区别
python中os.path.abspath与os.path.realpath 区别cd /homemkdir amkdir btouch a/1.txtln -s /home/a/1.txt /ho ...
- Python - input时的strip()
例如在读文件里面的文本时,每一行默认后面有\n或者有\r,所以strip用于去除首尾空格或者回车符.
- accordion(折叠面板)的使用
一.前言: 折叠面板(accordion)允许使用多面板(panel),同时显示一个或多个面板(panel).每个面板(panel)都有展开和折叠的内建支持.点击面板(panel)头部可展开或折叠面板 ...
- MDC 输出线程信息帮助定位问题
log4j中的%x ---NDC,%X---MDC 即%x NDC.clear();NDC.push(this.toString());%X{first} %X{last}MDC.put(" ...
- 用for循环写这段代码
之前用while循环写了一段代码,现在改为用for循环来写,代码如下: hongtao_age = 38 for i in range(5): guess_age = int(input(" ...
- 解决chrome记住账号默认样式覆盖
当谷歌游览器记住账号后,输入框的颜色会变为黄色,最直接的方法:加入以下代码 input:-webkit-autofill , textarea:-webkit-autofill, select:-we ...
- 在自定义的widget中引入图表后,运行时出现TypeError #1009错误
本人网上查找了很多资料,其中大部分都是关于Flash中的动画效果问题,与这里的问题关系型不太大.故把问题的解决方法写在这里,与人方便,与己方便. 方法一: 1.在自定义的widget中添加如下两个方法 ...