ORM框架
手写ORM框架: O(Object 对象) R(Relative 关系) M(Mapping 映射) 对象关系映射。把数据中的表映射为java中的实体类,表中的一条记录映射为java实体类对象。表中的列映射实体的属性了。
之前我们学习过一个ORM---Mybatis.-----单表的CRUD无需写任何sql语句。
我们自己手写的ORM框架使用的技术: 泛型 注解 反射。
反射: 把类中成员抽取为其他类对象的过程。就是反射。
反射类: Class--->实例化类对象。
获取属性类对象: Field
获取方法类对象: Method
获取构造方法类对象: Constructor
(1)创建maven工程并引入相关依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
(2)创建一个连接数据库的工具类。
db.properties
# mysql-connector-java 6.x及后续版本中的驱动类名
jdbc.driverName=com.mysql.cj.jdbc.Driver
# qy65是需要连接的具体数据库名,并且设置数据库时区为亚洲上海
jdbc.url=jdbc:mysql://localhost:3306/qy65?serverTimezone=Asia/Shanghai
# 数据库用户名(用于连接数据库)
jdbc.username=root
# 用户密码(用于连接数据库)
jdbc.password=123456
DbUtil.java
package com.aaa.util;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
/**
* @BelongsProject:orm
* @Description:
* @Author: Zhou
* @CreateTime: 2023/6/12 14:47
*/
public class DbUtil {
/**
* 硬编码
* 计算机科学中,只有硬编码(hardcode),以及非硬编码,有人也成为“软编码”。
* 硬编码和软编码的区别是:软编码可以在运行时确定,修改;而硬编码是不能够改变的。
*/
private static String driverName="";
private static String url="";
private static String username="";
private static String password="";
//静态代码块。随着类的加载而被加载,而且只会被加载一次
static {
try {
/*
* Properties读取配置文件属性值的方式
* 使用Class类的getSystemResourceAsStream方法 和使用当前类的ClassLoader是一样的
* InputStream inputStream = ClassLoader.getSystemResourceAsStream(name)
*/
//java.lang.ClassLoader 类是负责加载类的对象。 这个类是一个抽象类。 安全管理员可以使用它来指示安全域。
//java.lang.ClassLoader.getSystemResourceAsStream() 方法打开读取,从用于加载类的搜索路径中指定名称的资源。
InputStream inputStream = ClassLoader.getSystemResourceAsStream("db.properties");
//实例化Properties类
Properties properties = new Properties();
//调用load()方法加载properties文件,load里面传入InputSteam类型的参数或者Reader类型的参数
properties.load(inputStream);
//通过getProperty(String key)方法获取,传入一个String类型的键,返回一个String类型的值 如果键不存在则返回null
driverName = properties.getProperty("jdbc.driverName");
//获取相应的属性值
url = properties.getProperty("jdbc.url");
username = properties.getProperty("jdbc.username");
password = properties.getProperty("jdbc.password");
}catch (Exception e){
System.out.println("名字为db.properties的属性文件不存在");
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
System.out.println(getConnection());
}
/**
* 获取连接对象
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception{
//Class.forName()方法实现类加载
Class.forName(driverName);
//DiverManager.getConnection(String url,String user, String password)方法来获取连接。
Connection connection = DriverManager.getConnection(url,username,password);
return connection;
}
/**
* 关闭资源
* @param connection
* @param ps
* @param resultSet
*/
public static void closeAll(Connection connection, PreparedStatement ps, ResultSet resultSet){
try {
if (resultSet!=null){
resultSet.close();
}
if (ps!=null) {
ps.close();
}
if (connection!=null) {
connection.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
(3)创建一个父类BaseDao
一个子类DAO继承BaseDao类后,无需再写任何sql语句,而就可以完成单表的crud操作。
比如: StudentDao extends BaseDao DeptDao extends BaseDao.
BaseDao对所有表的CRUD操作都能通用。
package com.aaa.dao;
import com.aaa.annotation.TableField;
import com.aaa.annotation.TableId;
import com.aaa.annotation.TableName;
import com.aaa.util.DbUtil;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
/**
* @BelongsProject:orm
* @Description:
* @Author: Zhou
* @CreateTime: 2023/6/12 17:04
*/
public class BaseDao<T> {
Class<T> clazz;
public BaseDao() {
/**
* getGenericSuperclass() 通过反射获取当前类表示的实体(类,接口,基本类型或void)的直接父类的Type,
* getActualTypeArguments()返回参数数组。
* getActualTypeArguments()就是获取泛型参数的类型
*/
//获取BaseDao子类的反射类对象
Class<? extends BaseDao> aClass = this.getClass();
//获取当前Dao子类的父类的反射类
ParameterizedType parameterizedType = (ParameterizedType) aClass.getGenericSuperclass();
//得到泛型的反射类
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
clazz = (Class<T>) actualTypeArguments[0];
// System.out.println(clazz);
}
/**
* 通用的查询 根据主键查询一条记录
* SQL语句:select * from 表名 where 主键=值
*/
public T selectById(Object id) throws Exception{
StringBuffer sql=new StringBuffer("select * from ");
TableName annotation = clazz.getAnnotation(TableName.class);
String tableName=clazz.getSimpleName();
if(annotation!=null){
tableName=annotation.value();
}
sql.append(tableName+" where ");
/**
* getFields()和getDeclaredFields()的区别
* getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
* getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
*/
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
TableId tableId = field.getAnnotation(TableId.class);
if(tableId!=null){
sql.append(tableId.value()+"='"+id+"'");
continue;
}
}
System.out.println(sql);
//执行sql语句
Connection connection = DbUtil.getConnection();
PreparedStatement ps = connection.prepareStatement(sql.toString());
ResultSet rs = ps.executeQuery();
T t = null;
while (rs.next()){
t= clazz.newInstance();
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field:declaredFields){
field.setAccessible(true);
String name = field.getName();
TableField tableField = field.getAnnotation(TableField.class);
TableId tableId = field.getAnnotation(TableId.class);
if(tableField!=null){
name=tableField.value();
}
if(tableId!=null){
name=tableId.value();
}
Object v = rs.getObject(name);
field.set(t,v);
}
}
return t;
}
/**
* 通用的删除
* SQL语句:delete from 表名 where 主键=值
*/
public int delete(Object id) throws Exception{
StringBuffer sql = new StringBuffer("delete from ");
TableName annotation = clazz.getAnnotation(TableName.class);
String tableName = clazz.getSimpleName();
if (annotation!=null){
tableName=annotation.value();
}
sql.append(tableName+" where ");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
TableId tableId = field.getAnnotation(TableId.class);
if (tableId!=null){
sql.append(tableId.value()+"='"+id+"'");
/**
* continue语句的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环。
* 注意:continue语句并没有使整个循环终止。
*/
continue;
}
}
System.out.println(sql);
//执行SQL语句
Connection connection = DbUtil.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
int i = preparedStatement.executeUpdate();
return i;
}
/**
* 通用的添加操作
* 通用的添加SQL语句:insert into 表名(列名,列名....) values(值,值....)
* @param t
* @return
*/
public int insert(T t) throws Exception{
/*
String:不可变字符串
StringBuffer:可变字符串、效率低、线程安全
StringBuilder:可变字符序列、效率高、线程不安全
*/
//拼接SQL语句
StringBuffer sql = new StringBuffer("insert into ");
//根据对象获取Class反射类
Class<?> aClass = t.getClass();
//获取反射类上的注解对象
TableName annotation = aClass.getAnnotation(TableName.class);
//表名 getSimpleNam得到类的简写名称
String tableName = aClass.getSimpleName();
if (annotation!=null){
tableName=annotation.value();
}
//append() 方法可以向列表末尾添加「任意类型」的元素
sql.append(tableName);
//获取列名 获取该反射类里面的所有的属性对象
Field[] declaredFields = aClass.getDeclaredFields();
//列名
List<String> columns = new ArrayList<>();
//值
List<String> values = new ArrayList<>();
//遍历所有的属性对象
for (Field field : declaredFields) {
//属性名
String name = field.getName();
TableId annotation2 = field.getAnnotation(TableId.class);
if (annotation2!=null||name.equals("id")){
continue;
}
//getAnnotation()方法 返回该元素的指定类型的注释,如果是这样的注释,否则返回null。
TableField annotation1 = field.getAnnotation(TableField.class);
if (annotation1!=null){
name=annotation1.value();
}
//Accessable属性是继承自AccessibleObject 类. 功能是启用或禁用安全检查
//实际上setAccessible是启用和禁用访问安全检查的开关
field.setAccessible(true);
Object o = field.get(t);
columns.add(name);
values.add("'"+o+"'");
}
/**
* replace() 方法在字符串中搜索值或正则表达式。
* replace() 方法返回已替换值的新字符串。
* replace() 方法不会更改原始字符串。
*/
String columnNames = columns.toString().replace("[", "(").replace("]", ")");
String columnValues = values.toString().replace("[", "(").replace("]", ")");
sql.append(columnNames);
sql.append(" values ");
sql.append(columnValues);
System.out.println(sql);
//执行SQL语句 Java DriverManager.getConnection() 方法用于获得试图建立到指定数据库 URL 的连接。
Connection connection = DbUtil.getConnection();
//PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程
PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
/**
* 增、删、改 用executeUpdate()
* 返回值为int型,表示被影响的行数
*
* 查用executeQuery() 返回的是一个集合
* .next()表示 指针先下一行,还有first()指向第一行 、last()指向最后一行、如果有数据就返回true
*/
int i = preparedStatement.executeUpdate();
return i;
}
/**
* 通用的修改方法
* SQL语句:update 表名 set 列名=值,列名=值...where 主键名=值
*/
public int update(T t) throws Exception{
StringBuffer sql=new StringBuffer("update ");
//获取实体类的反射类
Class<?> aClass = t.getClass();
//获取反射类指定的注解对象
TableName annotation = aClass.getAnnotation(TableName.class);
String tableName = aClass.getSimpleName();
if(annotation!=null){
tableName=annotation.value();
}
sql.append(tableName+" set ");
//获取所有的Field
Field[] declaredFields = aClass.getDeclaredFields();
String where = " where ";
for (Field field : declaredFields) {
field.setAccessible(true);
String name = field.getName();
TableId tableId = field.getAnnotation(TableId.class);
if (name.equals("id")){
where = where +" id='"+field.get(t)+"'";
continue;
}
if (tableId!=null){
where = where + tableId.value()+"='"+field.get(t)+"'";
}
TableField tableField = field.getAnnotation(TableField.class);
if (tableField!=null){
name=tableField.value();
}
String value="'"+field.get(t)+"'";
sql.append(name+"="+value+",");
}
String sql2 = sql.toString().substring(0, sql.length() - 1)+where;
System.out.println(sql2);
//执行SQL语句
Connection connection = DbUtil.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql2);
int i = preparedStatement.executeUpdate();
return i;
}
}
(4)继承父类的子类
DeptDao.java
package com.aaa.dao;
import com.aaa.pojo.Dept;
/**
* @BelongsProject:orm
* @Description:
* @Author: Zhou
* @CreateTime: 2023/6/12 17:56
*/
public class DeptDao extends BaseDao<Dept>{
}
EmpDao.java
package com.aaa.dao;
import com.aaa.pojo.Emp;
/**
* @BelongsProject:orm
* @Description:
* @Author: Zhou
* @CreateTime: 2023/6/12 17:55
*/
public class EmpDao extends BaseDao<Emp> {
}
(5)创建实体类
Emp.java
package com.aaa.pojo;
import com.aaa.annotation.TableField;
import com.aaa.annotation.TableId;
public class Emp {
@TableId
private int id;
private String ename;
private String sex;
private double salary;
@TableField(value = "dept_id")
private int deptId;
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", ename='" + ename + '\'' +
", sex='" + sex + '\'' +
", salary=" + salary +
", deptId=" + deptId +
'}';
}
public Emp() {
}
public Emp(int id, String ename, String sex, double salary, int deptId) {
this.id = id;
this.ename = ename;
this.sex = sex;
this.salary = salary;
this.deptId = deptId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getDeptId() {
return deptId;
}
public void setDeptId(int deptId) {
this.deptId = deptId;
}
}
Dept.java
package com.aaa.pojo;
import com.aaa.annotation.TableField;
import com.aaa.annotation.TableId;
import com.aaa.annotation.TableName;
@TableName(value = "tbl_dept")
public class Dept {
@TableId(value = "did")
private int did;
@TableField(value = "d_name")
private String name;
private String addr;
@Override
public String toString() {
return "Dept{" +
"did=" + did +
", name='" + name + '\'' +
", addr='" + addr + '\'' +
'}';
}
public int getDid() {
return did;
}
public void setDid(int did) {
this.did = did;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
(6)注解类
TableField.java
package com.aaa.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//注解运用在属性上
@Target(ElementType.FIELD)
//运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
String value();
}
TableId.java
package com.aaa.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableId {
String value() default "id";
}
TableName.java
package com.aaa.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {//在实体类标记对应的表名
/**
* Target 是目标的意思,@Target 指定了注解运用的地方。
* ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
* ElementType.CONSTRUCTOR 可以给构造方法进行注解
* ElementType.FIELD 可以给属性进行注解
* ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
* ElementType.METHOD 可以给方法进行注解
* ElementType.PACKAGE 可以给一个包进行注解
* ElementType.PARAMETER 可以给一个方法内的参数进行注解
* ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
*/
/**
*Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
* 它的取值如下:
* RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
* RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
* RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
* 我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。
* @Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。
*/
String value();
}
(7)创建测试类
package com.aaa;
import com.aaa.dao.DeptDao;
import com.aaa.dao.EmpDao;
import com.aaa.pojo.Dept;
import com.aaa.pojo.Emp;
public class Test {
public static void main(String[] args) throws Exception {
DeptDao deptDao=new DeptDao();
System.out.println(deptDao.selectById(2));
// deptDao.delete(1);
// Dept dept = new Dept();
// dept.setName("人事部");
// dept.setAddr("郑州");
// dept.setDid(1);
// deptDao.update(dept);
// deptDao.insert(dept);
EmpDao empDao=new EmpDao();
System.out.println(empDao.selectById(9));
// empDao.delete(5);
// Emp emp = new Emp(9,"王啟光","男",5000,3);
// empDao.update(emp);
// Emp emp = new Emp(0,"王振阳","男",82,2);
// empDao.insert(emp);
}
}