用springboot手写自己的ORM框架
手写之前让我们简单回顾一下ORM框架,就是java对象和数据库字段之间的转化,当我们封装好了ORM之后,我们就可以直接操作对象,而不用管后台和数据库打交道的一些细节。
但是如果我们要操作对象,那么对象和数据库表,字段如何映射的呢,想想hibernate和mybatis是不是在实体中加入一些注解呢,也有xml配置的,本文中主要讲注解。废话有点多,直接开撸
既然我们要在实体中加入以下注解,总不能用mybatis和hibernate的注解吧,那么我们就自定义一些注解,帮我们做实体和字段的映射。
我们需要自定义两个注解,表名映射的注解和字段名映射的注解,因ID是字段中比较特殊的,所以我们自定义一个ID的注解,详见如下的代码
自定义注解
先定义表名注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*@Description 自定义注解,用来表示类和数据库表的关系
*@Author dym
*@Date 2019/8/30 15:05
*@Param
*@return
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MuyanTableName {
String tableName() default "";
}
在定义字段注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*@Description 自定义字段和属性的映射关系注解
*@Author dym
*@Date 2019/8/30 15:07
*@Param
*@return
*/
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.FIELD )
public @interface MuyanColumn {
String colomnName() default "";
}
最后定义id注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*@Description 自定义主键的注解
*@Author dym
*@Date 2019/8/30 15:08
*@Param
*@return
*/
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.FIELD )
public @interface MuyanId {
String idName() default "";
}
将定义好的注解,使用到我们的实体类上
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanColumn;
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanId;
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanTableName;
import lombok.Data;
import lombok.ToString;
/**
* @ClassName Student
* @Description 实体类,这里采用自定义注解
* @Author dym
* @Date 2019/8/30 14:57
* @Version 1.0
**/
@Data
@MuyanTableName(tableName="student")
@ToString
public class Student {
@MuyanId(idName = "id")
private Integer id;
@MuyanColumn(colomnName = "name")
private String name;
@MuyanColumn(colomnName = "age")
private Integer age;
@MuyanColumn(colomnName = "sex")
private String sex;
}
是不是感觉像模像样的,有点那个意思
完了就是老一套,写我们的controller和service,还有mapper,这都不是重点,所以我们很快撸完
controller
import com.muyan.springboot.myspringtransaction.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.sql.SQLException;
/**
* @ClassName StudentController
* @Description 学生控制层
* @Author dym
* @Date 2019/8/30 8:58
* @Version 1.0
**/
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
private UserService userService;
@RequestMapping("/addUser")
public void addData() throws SQLException {
userService.addData();
}
}
service
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanTransction;
import com.muyan.springboot.myspringtransaction.chg.domain.OptionLog;
import com.muyan.springboot.myspringtransaction.chg.domain.Student;
import com.muyan.springboot.myspringtransaction.chg.mapper.LogMapper;
import com.muyan.springboot.myspringtransaction.chg.mapper.StudentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* @ClassName ChangerUserService
* @Description 学生业务类
* @Author dym
* @Date 2019/8/30 10:16
* @Version 1.0
**/
@Service
public class ChangerStudentService {
@Autowired
private StudentMapper studentMapper;
/**
*@Description 保存实体
*@Author dym
*@Date 2019/9/3 17:53
*@Param [student]
*@return boolean
*/
@MuyanTransction
public boolean saveDataByOrm(Student student){
boolean result = studentMapper.add( student );
return result;
}
/**
*@Description 保存编辑实体
*@Author dym
*@Date 2019/9/3 17:54
*@Param [student]
*@return boolean
*/
@Transction
public boolean update(Student student){
boolean result = studentMapper.update( student );
return result;
}
/**
*@Description 根据id删除实体
*@Author dym
*@Date 2019/9/3 17:55
*@Param [id]
*@return boolean
*/
@Transction
public boolean delete(int id){
boolean result = studentMapper.delete( id );
return result;
}
/**
*@Description 根据ID查询student实体
*@Author dym
*@Date 2019/9/3 17:56
*@Param [id]
*@return com.muyan.springboot.myspringtransaction.chg.domain.Student
*/
public Student query(int id){
Student student = (Student) studentMapper.query( id );
return student;
}
/**
*@Description 查询所有的学生列表
*@Author dym
*@Date 2019/9/3 17:58
*@Param []
*@return java.util.List<com.muyan.springboot.myspringtransaction.chg.domain.Student>
*/
public List<Student> queryAll(){
List<Student> list = studentMapper.queryAll();
return list;
}
}
mapper
import com.muyan.springboot.myspringtransaction.chg.domain.Student;
import org.springframework.stereotype.Component;
/**
* @ClassName StudentMapper
* @Description student的mapper层
* @Author dym
* @Date 2019/9/2 9:51
* @Version 1.0
**/
@Component
public class StudentMapper {
}
那么我们如何写mapper呢,是不是在mapper里面就可以写对我们实体做增删改查了呢,可以是可以,但是我们手写的简单的ORM框架,既然是框架,那么我们就要封装,因为我们系统中不可能只有一个实体,不可能每个实体对应的mapper都写如何从映射中拿到值做操作数据库。所以我们需要一个通用的mapper来做这件事,所有的mapper都来继承它,这样以后如果新建一张表,新建一个实体之后,新建一个mapper直接继承,而不是再解析映射。
既然是通用mapper,那么我们建的mapper必须满足所有的实体,我们需要设计它为泛型。
新建一个baseMapper
import org.springframework.beans.factory.annotation.Autowired;
import java.sql.*;
import java.util.*;
/**
* @ClassName BaseMapper
* @Description 定义抽象类,用来封装ORM数据映射
* @Author dym
* @Date 2019/9/2 9:43
* @Version 1.0
**/
public class BaseMapper<T> {
/**
*@Description 保存实体
*@Author dym
*@Date 2019/9/2 9:52
*@Param [t]
*@return boolean
*/
public boolean add(T t){
return null;
}
/**
*@Description 删除某个实体
*@Author dym
*@Date 2019/9/2 9:54
*@Param [id]
*@return void
*/
public boolean delete(Object id){
return null;
}
/**
*@Description 编辑某个实体
*@Author dym
*@Date 2019/9/2 9:54
*@Param [t]
*@return void
*/
public boolean update(T t){
return null;
}
/**
*@Description 查询某个实体
*@Author dym
*@Date 2019/9/2 9:55
*@Param [id]
*@return T
*/
public T query( Object id){
return null;
}
/**
*@Description 查询所有数据
*@Author dym
*@Date 2019/9/2 9:55
*@Param []
*@return java.util.List<T>
*/
public List<T> queryAll(){
return null;
}
}
问题:我们新建完我们需要的所有类之后,我们开始本文中重点的重点,如果不做ORM框架,那么我们是不是就用jdbc做CURD了呢,不错,但是jdbc操作的是字段,而不是对象,我们传递进来的是对象,那怎么办呢!
思路:那么我们需要做个事,什么事呢,就是将对象转化为字段,此处分两步走,第一步每个方法里面都需要做个对象和字段的转化,第二步拿到字段之后,做JDBC操作。
有了这个思路我们是不是就可以着手做了呢,但是我们想想如果每个方法都做堆一堆这样的代码,估计看得人头有点大,所以我们统一写一个类(BuildParamToFieldManager)来帮助我们做专一的事,再写一个jdbc的封装类(MuyanJdbcTemplate)来帮助我们写jdbc具体的操作。因为我们不为具体某一个实体服务,所以统一采用泛型。
做具体操作前,我们需要先写一个工具,什么工具类呢,就是帮我们从实体中获取表名和字段名
注意:前方车要转弯,请系好安全带,ps:就是要懂一点java反射的知识,否则容易晕车
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanColumn;
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanId;
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanTableName;
import java.lang.reflect.Field;
import java.util.Objects;
/**
* @ClassName MuyanStringUtils
* @Description 自定义工具类
* @Author dym
* @Date 2019/9/3 9:39
* @Version 1.0
**/
public class MuyanStringUtils {
/**
*@Description 将第一个字母替换为大写
*@Author dym
*@Date 2019/9/3 9:40
*@Param [str]
*@return java.lang.String
*/
public static String firstUpperCase(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1, str.length());
}
/**
*@Description 获取属性的set方法
*@Author dym
*@Date 2019/9/3 9:40
*@Param [fieldName]
*@return java.lang.String
*/
public static String getSetMethod(String fieldName){
return "set"+firstUpperCase( fieldName );
}
/**
*@Description 根据实体获取表名称
*@Author dym
*@Date 2019/9/2 15:26
*@Param [clazz]
*@return java.lang.String
*/
public static String getTableName(Class<?> clazz){
//默认获取类中的小写名称
String tableName = clazz.getSimpleName();
if (null != clazz){
//判断类是否加载了自定义注解
if (clazz.isAnnotationPresent( MuyanTableName.class)){
MuyanTableName muyanTableName = clazz.getAnnotation( MuyanTableName.class );
tableName = muyanTableName.tableName();
}
}
return tableName;
}
/**
*@Description 根据属性名称获取字符名称
*@Author dym
*@Date 2019/9/2 15:28
*@Param [field]
*@return java.lang.String
*/
public static String getTableFieldName(Field field){
String fieldName = "";
if (null != field){
if (field.isAnnotationPresent( MuyanColumn.class )){
MuyanColumn annotation = field.getAnnotation( MuyanColumn.class );
if (!Objects.equals("",annotation)){
fieldName = annotation.colomnName();
}
}else if(!field.isAnnotationPresent( MuyanId.class )){
fieldName = field.getName();
}
}
return fieldName;
}
/**
*@Description 根据字段获取主键的名称
*@Author dym
*@Date 2019/9/2 17:17
*@Param [field]
*@return java.lang.String
*/
public static String getTableIdName(Field field){
String idName = "";
if (null != field){
if (field.isAnnotationPresent( MuyanId.class )){
MuyanId muyanId = field.getAnnotation( MuyanId.class );
if (!Objects.equals( "",muyanId )){
idName = muyanId.idName();
}
}else if (!field.isAnnotationPresent( MuyanColumn.class )){
idName = field.getName();
}
}
return idName;
}
}
那我们开始写BuildParamToFieldManager
类
手写我们写一个保存实体,就是往数据库中插一条数据
思路:传递进来的肯定是一个实体,我们通过实体拿到表名和字段名,完了拼接一个insert语句返回给baseMapper,由basemapper来执行jdbc操作
public void buildInsertFieldParams(T entity, StringBuffer sql, List<Object> paramsList) {
//首先根据clazz获取表名称
String tableName = MuyanStringUtils.getTableName( entity.getClass() );
//其次开始拼接sql
sql.append( "insert into " ).append( tableName ).append( " ( " );
try {
//获取字段名称
Field[] fields = entity.getClass().getDeclaredFields();
if (!Objects.equals( "", fields )){
for (Field field : fields) {
//根据属性值获取数据库字段名称
String tableFieldName = MuyanStringUtils.getTableFieldName( field );
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
//字段取到之后,开始将值填充参数列表中
Object o = field.get( entity );
if (o != null && !"".equals( tableFieldName )){
//继续拼接sql
sql.append( tableFieldName ).append( "," );
paramsList.add( o );
}
}
//去掉字段中最后一个逗号,
sql.deleteCharAt( sql.length() - 1 ).append( " ) values ( " );
for (Object o : paramsList) {
//sql.append( o ).append( "," );
sql.append( " ? ," );
}
//去掉占位符中的最后一个逗号
sql.deleteCharAt( sql.length() - 1 ).append( " ) " );
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
修改的方法怎么写呢,其实原理一样,就是sql格式不一样而已
public void buildUpdateFieldParams(T entity, StringBuffer sql, List<Object> paramsList) {
//首先根据clazz获取表名称
String tableName = MuyanStringUtils.getTableName( entity.getClass() );
//其次开始拼接sql
sql.append( "update " ).append( tableName ).append( " set " );
try {
//获取字段名称
Field[] fields = entity.getClass().getDeclaredFields();
if (!Objects.equals( "", fields )){
String idName = "";
Object idValues = null;
for (Field field : fields) {
//根据属性值获取数据库字段名称
String tableFieldName = MuyanStringUtils.getTableFieldName( field );
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
if (StringUtils.isEmpty( idName )){
idName = MuyanStringUtils.getTableIdName( field );
}else{
//字段取到之后,开始将值填充参数列表中
Object o = field.get( entity );
if (o != null){
//继续拼接sql
sql.append( tableFieldName ).append( " = ?," );
paramsList.add( o );
}
}
if (field.isAnnotationPresent( MuyanId.class )){
idValues = field.get( entity );
}
}
//去掉字段中最后一个逗号,
sql.deleteCharAt( sql.length() - 1 ).append( " where " + idName +" = ? ");
paramsList.add( idValues );
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
那查询呢,道理一样,继续前进
/**
*@Description 拼接查询的语句
*@Author dym
*@Date 2019/9/2 18:10
*@Param [clazz, sql]
*@return void
*/
public void buildSelectFieldParams(T entity, StringBuffer sql, Object primaryId, List<Object> paramList) {
//首先根据clazz获取表名称
String tableName = MuyanStringUtils.getTableName( entity.getClass() );
//获取ID的名称
String idName = "";
sql.append( "select " );
Field[] fields = entity.getClass().getDeclaredFields();
if (null == fields){
throw new RuntimeException( "获取属性值为空!" );
}
for (Field field : fields) {
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
//拼接主键名称
if ("".equals( idName )){
idName = MuyanStringUtils.getTableIdName( field );
sql.append( idName ).append( "," );
}
//拼接其他字段的名称
String tableFieldName = MuyanStringUtils.getTableFieldName( field );
if (!"".equals( tableFieldName )){
sql.append( tableFieldName ).append( "," );
}
}
//去掉最后一个逗号
sql.deleteCharAt( sql.length() - 1 ).append( " from " ).append( tableName );
if (null != primaryId){
sql.append( " where " ).append( idName ).append( " = ? " );
paramList.add( primaryId );
}
}
删除也是同理
public void buildDeleteFieldParams(Class<?> clazz, StringBuffer sql, List<Object> paramsList, Object id) {
//获取表名
String tableName = MuyanStringUtils.getTableName( clazz );
//声明主键名称
String idName = "";
//获取实体类中的属性
Field[] fields = clazz.getDeclaredFields();
//循环查询出实体类中主键对应的数据库字段名称
for (Field field : fields) {
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
if (Objects.equals( "",idName )){
idName = MuyanStringUtils.getTableIdName( field );
}else{
break;
}
}
if (Objects.equals( "", idName)){
throw new RuntimeException( "实体类"+clazz.getSimpleName()+"中没有找到主键注解!" );
}
//将找到的主键名称拼接到删除的sql里面
sql.append( "delete from " ).append( tableName ).append( " where " ).append( idName ).append( " = ? " );
paramsList.add( id );
}
有些传递的是实体T,有的传递是Class,通常我们做新增和修改的时候,是传递的实体,删除和单个查询传递的是某个整型,其实我们可以将这个整形或者字符串转成对象传递进来,如
新增
Student student = new Student();
student.setName("muyan456");
student.setAge(40);
student.setSex("男");
boolean b = changerStudentService.saveDataByOrm( student );
删除
Student student = new Student();
student.setId(37);
boolean result = changerStudentService.delete( student );
这样就很简单,如果我们增加点难度,我偏要不转对象,直接传递进来一个int或者String的id,那么我们框架如何支持呢,一个id我们很难知道是对应哪个实体,对应那张表。
思路:我们需要知道你调用的现在是继承baseMapper的哪个实体,乃至哪个具体的mapper在调用,这样我们就知道是哪个实体了,也就知道对应那张表了呀!
有了思路我们直接开干
public Class<T> getClassInfo( Class<? extends BaseMapper> clazz ){
//获取本类
//返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type (泛型类的参数化)
// 比如说 StudentMapper extends BaseMapper<Student>
//得到 BaseMapper<Student>的类型
Type genericSuperclass = clazz.getGenericSuperclass();
//判断获取超类中的类型是不是参数化类型
if (genericSuperclass instanceof ParameterizedType) {
//如果是参数化类型,则强制转化为参数化类型
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//获取泛型中的实际类型,可能会存在多个泛型,例如Map<K,V>,所以会返回Type[]数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
//我们做的都是单表映射,并且必须有一个泛型,所以我们取第一个泛型就可以获取到具体泛型的类型
Type actualTypeArgument = actualTypeArguments[0];
//并且把这个具体的泛型强制转化为我们需要的带泛型的类(因为泛型的类型本身就是个类,那我们将强制)
Class<T> returnType = (Class<T>) actualTypeArgument;
return returnType;
}
return null;
}
所以我们的BuildParamToFieldManager
类详细是这样的。
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanId;
import com.muyan.springboot.myspringtransaction.chg.utils.MuyanStringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;
/**
* @ClassName BuildParamToFieldManager
* @Description 拼接增删改查的sql,从Object转化为数据库中的Field
* @Author dym
* @Date 2019/9/5 17:39
* @Version 1.0
**/
@Component
public class BuildParamToFieldManager<T> {
/**
*@Description 获取本类中的实体对象
*@Author dym
*@Date 2019/9/3 16:20
*@Param []
*@return java.lang.Class<T>
*/
public Class<T> getClassInfo( Class<? extends BaseMapper> clazz ){
//获取本类
//返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type (泛型类的参数化)
// 比如说 StudentMapper extends BaseMapper<Student>
//得到 BaseMapper<Student>的类型
Type genericSuperclass = clazz.getGenericSuperclass();
//判断获取超类中的类型是不是参数化类型
if (genericSuperclass instanceof ParameterizedType) {
//如果是参数化类型,则强制转化为参数化类型
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//获取泛型中的实际类型,可能会存在多个泛型,例如Map<K,V>,所以会返回Type[]数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
//我们做的都是单表映射,并且必须有一个泛型,所以我们取第一个泛型就可以获取到具体泛型的类型
Type actualTypeArgument = actualTypeArguments[0];
//并且把这个具体的泛型强制转化为我们需要的带泛型的类(因为泛型的类型本身就是个类,那我们将强制)
Class<T> returnType = (Class<T>) actualTypeArgument;
return returnType;
}
return null;
}
/**
*@Description 拼接删除的sql
*@Author dym
*@Date 2019/9/3 16:21
*@Param [classInfo, sql, paramsList]
*@return void
*/
public void buildDeleteFieldParams(Class<?> clazz, StringBuffer sql, List<Object> paramsList, Object id) {
//获取表名
String tableName = MuyanStringUtils.getTableName( clazz );
//声明主键名称
String idName = "";
//获取实体类中的属性
Field[] fields = clazz.getDeclaredFields();
//循环查询出实体类中主键对应的数据库字段名称
for (Field field : fields) {
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
if (Objects.equals( "",idName )){
idName = MuyanStringUtils.getTableIdName( field );
}else{
break;
}
}
if (Objects.equals( "", idName)){
throw new RuntimeException( "实体类"+clazz.getSimpleName()+"中没有找到主键注解!" );
}
//将找到的主键名称拼接到删除的sql里面
sql.append( "delete from " ).append( tableName ).append( " where " ).append( idName ).append( " = ? " );
paramsList.add( id );
}
/**
*@Description 拼接查询的语句
*@Author dym
*@Date 2019/9/2 18:10
*@Param [clazz, sql]
*@return void
*/
public void buildSelectFieldParams(T entity, StringBuffer sql, Object primaryId, List<Object> paramList) {
//首先根据clazz获取表名称
String tableName = MuyanStringUtils.getTableName( entity.getClass() );
//获取ID的名称
String idName = "";
sql.append( "select " );
Field[] fields = entity.getClass().getDeclaredFields();
if (null == fields){
throw new RuntimeException( "获取属性值为空!" );
}
for (Field field : fields) {
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
//拼接主键名称
if ("".equals( idName )){
idName = MuyanStringUtils.getTableIdName( field );
sql.append( idName ).append( "," );
}
//拼接其他字段的名称
String tableFieldName = MuyanStringUtils.getTableFieldName( field );
if (!"".equals( tableFieldName )){
sql.append( tableFieldName ).append( "," );
}
}
//去掉最后一个逗号
sql.deleteCharAt( sql.length() - 1 ).append( " from " ).append( tableName );
if (null != primaryId){
sql.append( " where " ).append( idName ).append( " = ? " );
paramList.add( primaryId );
}
}
/**
*@Description 参数新增拼装
*@Author dym
*@Date 2019/9/2 15:36
*@Param [aClass, sql, paramsList]
*@return void
*/
public void buildInsertFieldParams(T entity, StringBuffer sql, List<Object> paramsList) {
//首先根据clazz获取表名称
String tableName = MuyanStringUtils.getTableName( entity.getClass() );
//其次开始拼接sql
sql.append( "insert into " ).append( tableName ).append( " ( " );
try {
//获取字段名称
Field[] fields = entity.getClass().getDeclaredFields();
if (!Objects.equals( "", fields )){
for (Field field : fields) {
//根据属性值获取数据库字段名称
String tableFieldName = MuyanStringUtils.getTableFieldName( field );
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
//字段取到之后,开始将值填充参数列表中
Object o = field.get( entity );
if (o != null && !"".equals( tableFieldName )){
//继续拼接sql
sql.append( tableFieldName ).append( "," );
paramsList.add( o );
}
}
//去掉字段中最后一个逗号,
sql.deleteCharAt( sql.length() - 1 ).append( " ) values ( " );
for (Object o : paramsList) {
//sql.append( o ).append( "," );
sql.append( " ? ," );
}
//去掉占位符中的最后一个逗号
sql.deleteCharAt( sql.length() - 1 ).append( " ) " );
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
*@Description 拼接update操作的sql
*@Author dym
*@Date 2019/9/2 17:07
*@Param [clazz, sql, paramsList]
*@return void
*/
public void buildUpdateFieldParams(T entity, StringBuffer sql, List<Object> paramsList) {
//首先根据clazz获取表名称
String tableName = MuyanStringUtils.getTableName( entity.getClass() );
//其次开始拼接sql
sql.append( "update " ).append( tableName ).append( " set " );
try {
//获取字段名称
Field[] fields = entity.getClass().getDeclaredFields();
if (!Objects.equals( "", fields )){
String idName = "";
Object idValues = null;
for (Field field : fields) {
//根据属性值获取数据库字段名称
String tableFieldName = MuyanStringUtils.getTableFieldName( field );
//因为属性有可能是private不能访问的,那么我们就通过强吻她
field.setAccessible( true );
if (StringUtils.isEmpty( idName )){
idName = MuyanStringUtils.getTableIdName( field );
}else{
//字段取到之后,开始将值填充参数列表中
Object o = field.get( entity );
if (o != null){
//继续拼接sql
sql.append( tableFieldName ).append( " = ?," );
paramsList.add( o );
}
}
if (field.isAnnotationPresent( MuyanId.class )){
idValues = field.get( entity );
}
}
//去掉字段中最后一个逗号,
sql.deleteCharAt( sql.length() - 1 ).append( " where " + idName +" = ? ");
paramsList.add( idValues );
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
既然我们有将实体类转化成数据库字段的类,那我们是不是还要写一个将数据库字段再转化成实体类呢,没错,做了查询,查询结果肯定要返回到前端去,并且根据id查询的,肯定返回实体,如果是不带分页查询的,返回的肯定是带有这个实体泛型的集合,所以我们有必要做这个处理。新建一个InvokeClassAdapter
这个类,具体实现,参照如下的代码:
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanColumn;
import com.muyan.springboot.myspringtransaction.chg.annotation.MuyanId;
import com.muyan.springboot.myspringtransaction.chg.utils.MuyanStringUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.*;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName InvokeClassAdapter
* @Description 提供数据库查询结果和实体类的转化
* @Author dym
* @Date 2019/9/5 17:48
* @Version 1.0
**/
@Component
public class InvokeClassAdapter {
/**
*@Description 将数据库中的一条记录封装成一个实体类
*@Author dym
*@Date 2019/9/3 10:14
*@Param [resultSet, clazz]
*@return T
*/
public static <T> T invokeObject(ResultSet resultSet, Class<T> clazz){
Map<String,Field> map = new HashMap<>( );
try {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent( MuyanColumn.class )) {
MuyanColumn annotation = field.getAnnotation( MuyanColumn.class );
String annotationColomnName = annotation.colomnName() == null?field.getName(): annotation.colomnName();
//将注解名称和属性存放到map中
map.put( annotationColomnName, field);
}else if (field.isAnnotationPresent( MuyanId.class )){
MuyanId annotation = field.getAnnotation( MuyanId.class );
map.put( field.getName(), field );
}
}
T obj = clazz.getDeclaredConstructor().newInstance();
ResultSetMetaData metaData = null;
metaData = resultSet.getMetaData();
for (int i = 0;i < metaData.getColumnCount(); i++){
String columnName = metaData.getColumnName( i + 1 );
if (map.containsKey( columnName )) {
Field field = map.get( columnName );
Class<?> type = field.getType();
//获取方法名称
String setMethod = MuyanStringUtils.getSetMethod( field.getName() );
Method method = clazz.getMethod( setMethod, type );
Object object = resultSet.getObject( i + 1 );
method.invoke( obj, type.cast( object ) );
}
}
return obj;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
我们的思路继续,一切封装都好了,那么我们需要开始写具体的jdbc了,为了代码的条理性,我们将这个执行jdbc的操作提出来,专门写个类,帮我们做增删改查。这个类叫MuyanJdbcTemplate
import com.muyan.springboot.myspringtransaction.chg.common.InvokeClassAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName MuyanJdbcTemplate
* @Description 具体执行数据库操作
* @Author dym
* @Date 2019/8/30 14:48
* @Version 1.0
**/
@Component
public class MuyanJdbcTemplate<T> {
@Autowired
private MuYanTransctionHolder muYanTransctionHolder;
@Autowired
private InvokeClassAdapter invokeClassAdapter;
/**
*@Description 执行具体的操作(查询),并且返回实体类
*@Author dym
*@Date 2019/9/2 17:50
*@Param [sql]
*@return T
*/
public <T> T executeQueryStatement(StringBuffer sql, Class<T> clazz, List<Object> list) throws IllegalAccessException, InstantiationException {
T instance = null;
Connection connection = muYanTransctionHolder.getConnection();
PreparedStatement preparedStatement = null;
try{
preparedStatement = connection.prepareStatement( sql.toString() );
System.out.println( "查询sql="+sql.toString() );
if (null != list && list.size() > 0){
for (int i= 0; i<list.size(); i++){
preparedStatement.setObject( i+1, list.get( i ) );
System.out.println( "参数"+i+":"+list.get( i ) );
}
}
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()){
instance = (T) invokeClassAdapter.invokeObject( resultSet, clazz );
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return instance;
}
/**
*@Description 查询结果返回集合
*@Author dym
*@Date 2019/9/3 17:41
*@Param [sql, entity]
*@return java.util.List<T>
*/
public List<T> executeQueryStatementByList(StringBuffer sql, Class<T> entity) throws IllegalAccessException, InstantiationException {
T instance = null;
List<T> instanceList = new ArrayList<>( );
Connection connection = muYanTransctionHolder.getConnection();
PreparedStatement preparedStatement = null;
try{
preparedStatement = connection.prepareStatement( sql.toString() );
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
instance = (T) invokeClassAdapter.invokeObject( resultSet, entity );
instanceList.add( instance );
}
return instanceList;
}catch (Exception e){
e.printStackTrace();
}finally {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return Collections.emptyList();
}
/**
*@Description 执行具体的操作(增,改)
*@Author dym
*@Date 2019/9/2 17:04
*@Param [sql, paramsList]
*@return boolean
*/
public boolean executeStatement(StringBuffer sql, List<Object> paramsList){
Connection connection = muYanTransctionHolder.getConnection();
PreparedStatement preparedStatement = null;
try {
preparedStatement = connection.prepareStatement( sql.toString() );
if (paramsList != null && paramsList.size() > 0){
for(int i = 0; i < paramsList.size(); i++){
preparedStatement.setObject( i+1, paramsList.get( i ));
}
}
boolean execute = preparedStatement.execute();
if (execute ){
return Boolean.TRUE;
}
} catch (SQLException e) {
e.printStackTrace();
return Boolean.FALSE;
}finally {
//因为此处和AOP中使用的connection是同一个,所以此处不能关闭connection,只关闭preparedstatement即可
try {
preparedStatement.close();
//connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return Boolean.FALSE;
}
}
其中使用了一个MuyanTransctionHolder
的类,这个类做什么呢,就是帮我们从数据库中获取连接的,这里有些同学会问不就是获取connection嘛,为什么一行代码还要提出来呢,我提出来的原因在于,因为我手写完ORM之后,我要在下一期手写自己的事务,没错,借助于spring的AOP技术,我们要手写自己的事务。所以我提出来,这本实例中,大家可以暂时不用提出来也行。详细代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
/**
* @ClassName MuYanTransctionHolder
* @Description
* @Author dym
* @Date 2019/8/30 10:20
* @Version 1.0
**/
@Component //交给spring去管理
public class MuYanTransctionHolder {
@Autowired
private DataSource dataSource;
//获取连接
public Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
}
到这里我们核心代码都写完了,有了上面的一系列操作,完了我们回过头来继续看我们的BaseMapper
这个类,此时应该就简单多了。
import com.muyan.springboot.myspringtransaction.chg.template.MuyanJdbcTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.sql.*;
import java.util.*;
/**
* @ClassName BaseMapper
* @Description 定义抽象类,用来封装ORM数据映射
* @Author dym
* @Date 2019/9/2 9:43
* @Version 1.0
**/
public class BaseMapper<T> {
@Autowired
private BuildParamToFieldManager<T> buildInsertFieldParams;
@Autowired
private MuyanJdbcTemplate<T> muyanJdbcTemplate;
/**
*@Description 保存实体
*@Author dym
*@Date 2019/9/2 9:52
*@Param [t]
*@return boolean
*/
public boolean add(T t){
//定义sql
StringBuffer sql = new StringBuffer( );
//参数集
List<Object> paramsList = new ArrayList<>( );
//开始拼接单表新增的sql(Object->Field)
buildInsertFieldParams.buildInsertFieldParams(t, sql, paramsList);
//开始执行具体的操作
boolean b = muyanJdbcTemplate.executeStatement( sql, paramsList );
return b;
}
/**
*@Description 删除某个实体
*@Author dym
*@Date 2019/9/2 9:54
*@Param [id]
*@return void
*/
public boolean delete(Object id){
//定义sql
StringBuffer sql = new StringBuffer( );
//参数集
List<Object> paramsList = new ArrayList<>( );
//开始拼接sql(Object->Field)
buildInsertFieldParams.buildDeleteFieldParams(buildInsertFieldParams.getClassInfo(this.getClass()), sql, paramsList, id);
//执行sql操作
boolean b = muyanJdbcTemplate.executeStatement( sql, paramsList );
return b;
}
/**
*@Description 编辑某个实体
*@Author dym
*@Date 2019/9/2 9:54
*@Param [t]
*@return void
*/
public boolean update(T t){
//定义sql
StringBuffer sql = new StringBuffer( );
//参数集
List<Object> paramsList = new ArrayList<>( );
//开始拼接编辑的sql(Object->Field)
buildInsertFieldParams.buildUpdateFieldParams(t, sql, paramsList);
//执行sql操作
boolean b = muyanJdbcTemplate.executeStatement( sql, paramsList );
return b;
}
/**
*@Description 查询某个实体
*@Author dym
*@Date 2019/9/2 9:55
*@Param [id]
*@return T
*/
public T query( Object id){
//定义sql
StringBuffer sql = new StringBuffer( );
//定义参数列表
List<Object> paramList = new ArrayList<>( );
//获取当前类的实体类
Class<T> classInfo = buildInsertFieldParams.getClassInfo(this.getClass());
try {
//开始拼接参数(Object->Field)
buildInsertFieldParams.buildSelectFieldParams( classInfo.newInstance(), sql, id, paramList);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
T entity = null;
try {
//执行查询具体操作,并且将(sql->Object),最终将查询结果再次反转化为Object返回
entity = muyanJdbcTemplate.executeQueryStatement( sql , classInfo, paramList );
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return entity;
}
/**
*@Description 查询所有数据
*@Author dym
*@Date 2019/9/2 9:55
*@Param []
*@return java.util.List<T>
*/
public List<T> queryAll(){
//定义sql
StringBuffer sql = new StringBuffer( );
//定义参数列表
List<Object> paramList = new ArrayList<>( );
//获取当前类的实体类
Class<T> classInfo = buildInsertFieldParams.getClassInfo(this.getClass());
try {
//开始拼接查询的sql(Object->Field)
buildInsertFieldParams.buildSelectFieldParams(classInfo.newInstance(), sql, null, paramList);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
List<T> list = null;
try {
//执行具体的操作,并且最终将数据库中查询出来的数据封装成实体类的集合返回
list = muyanJdbcTemplate.executeQueryStatementByList( sql , classInfo);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return list;
}
}
现在看是不是BaseMapper
简单明了多了。最后附上application.properties
和测试类以及数据库脚本
spring.datasource.url=jdbc:mysql://localhost:3306/student?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver=com.mysql.jdbc.Driver
logging.level.root=debug
server.port=9001
import com.muyan.springboot.myspringtransaction.chg.domain.Student;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.junit.Assert.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ChangerStudentServiceTest {
@Autowired
private ChangerStudentService changerStudentService;
@Test
public void saveData() {
}
@Test
public void saveDataByOrm() {
Student student = new Student();
student.setName("muyan456");
student.setAge(40);
student.setSex("男");
boolean b = changerStudentService.saveDataByOrm( student );
Assert.assertFalse( b );
}
@Test
public void update() {
Student student = new Student();
student.setId(35);
student.setName("muyan1234");
student.setAge(20);
student.setSex("男");
boolean result = changerStudentService.update( student );
Assert.assertFalse( result );
}
@Test
public void delete() {
boolean result = changerStudentService.delete( 37 );
Assert.assertFalse( result );
}
@Test
public void query() {
Student student = changerStudentService.query( 35 );
System.out.println( "查询结果为:"+student );
Assert.assertNotNull( student );
}
@Test
public void queryAll() {
List<Student> students = changerStudentService.queryAll();
if (null != students && students.size() > 0){
for (Student student : students) {
System.out.println( student );
}
}
}
}
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
忘记粘贴POM.XML了,现在加上去
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.muyan.springboot</groupId>
<artifactId>myspring-transaction</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>myspring-transaction</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
好了大功告成!因个人技术能力有限,不能做到完美,大佬勿喷,给需要的人做个参考而已,刚开始写博客,积极心最重要,至于源码,后续更新完手写事务之后,一并奉上!