造一个方形的轮子5--数据库支持

造一个方形轮子文章目录:造一个方形的轮子

01、先把车正过

在上一篇《造一个方形的轮子4--依赖注入》的最后提出了一个问题,按类型简称(不带包名)以及按注解上设置的Bean名字去初始化Bean的时候都会有覆盖问题,比如不同包下的相同的类,或者在注解上设置了相同Bean名字的类,解决方法不允许重复就可以了,暴力一点有重名的直接抛异常。

BeansInitUtil.java 修改loadClass方法:

    //......上略
    private static void loadClass(File file, Map<String, BeanObject> map){
        ....
                // 按类设置bean
                map.put(beanObject.getClassName(), beanObject);
                String simpleName = firstToLowerCase(beanObject.getSimpleName());
                // 这里添加判断
                if(map.get(simpleName) != null){
                    throw new SquareBeanInitException("There are duplicate beans ,beanName:"+simpleName);
                }
                map.put(simpleName, beanObject);
                // 按注解输入value设置bean
                for (Annotation annotation : annotations) {
                    String tmp_name = "";
                    if(annotation instanceof Service){
                        tmp_name = ((Service)annotation).value();
                    } else if(annotation instanceof Component) {
                        tmp_name = ((Component)annotation).value();
                    }
                    if(tmp_name != null && !tmp_name.equals("")) {
                        // 这里添加判断
                        if(map.get(tmp_name) != null){
                            throw new SquareBeanInitException("There are duplicate beans ,beanName:"+tmp_name);
                        }
                        map.put(tmp_name, beanObject);
                    }
                }
            }
        } catch (Exception e) {
            log.error("init bean error:{}", file.getPath(), e);
        }
    }
    //......下略

复制一个com.jisuye.service.impl.AbcImpl到com.jisuye.service包启动程序查看日志输出:

12:48:17.058 [main] ERROR com.jisuye.core.SquareApplication - There are duplicate beans ,beanName:abcImpl
12:48:17.060 [main] ERROR com.jisuye.core.SquareApplication - Application startup failed...

看日志输出了重复的bean异常提示,现在删除刚复制的AbcImpl.java,将原来com.jisuye.service.impl.DefImpl及com.jisuye.service.impl.DefImpl都使用@Service("def") 注解,然后启动程序查看日志输出:

12:53:53.801 [main] ERROR com.jisuye.core.SquareApplication - There are duplicate beans ,beanName:def
12:53:53.803 [main] ERROR com.jisuye.core.SquareApplication - Application startup failed...

也输出了输出了重复的bean异常提示,验证完毕。

02、数据库支持准备

数据库支持就以mysql为例,添加一个jdbcTemplate类似的功能。

首先引入数据库链接池,这们就不用管链接的问题了,使用的是HikariCP链接池,在pom.xml中添加依赖

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>${hikariCP.version}</version>
        </dependency>

配置文件中添加数据库相关配置

square:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true
    username: root
    password: 123456

03、DbUtil工具类

将创建数据库链接池及相关方法封装到统一的工具类DbUtil.java:

package com.jisuye.util;
//import ...
/**
 * 数据库操作工具
 * @author ixx
 * @date 2019-07-01
 */
public class DbUtil {
    private static final Logger log = LoggerFactory.getLogger(DbUtil.class);
    private static Connection connection;

    /** 初始化方法*/
    public static void init(){
        try {
            String url = ApplicationContext.getConf("square.datasource.url").toString();
            String username = ApplicationContext.getConf("square.datasource.username").toString();
            String password = ApplicationContext.getConf("square.datasource.password").toString();

            HikariDataSource ds = new HikariDataSource();
            ds.setJdbcUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            // HikariCP提供的优化设置
            ds.addDataSourceProperty("cachePrepStmts", "true");
            ds.addDataSourceProperty("prepStmtCacheSize", "250");
            ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
            connection = ds.getConnection();
        } catch (Exception e) {
            log.error("mysql connection init error..", e);
            throw new SquareBeanInitException("mysql connection init error....");
        }
    }

    /** install/update 带参数占位符方法*/
    public static boolean update(String sql, Object... params){
        PreparedStatement statement;
        try {
            statement = connection.prepareStatement(sql);
            if(params != null) {
                for (int i = 1; i <= params.length; i++) {
                    statement.setObject(i, params[i - 1]);
                }
            }
            return statement.execute();
        } catch (Exception e) {
            log.error("install/update exception.", e);
        }
        return false;
    }
    /** install/update 无参数占位符方法*/
    public static boolean update(String sql){
        return update(sql, null);
    }

    /**
     * 通用查询方法
     * @param sql sql语句
     * @param clazz 返回列表类型
     * @param params 参数列表
     * @param <T> 返回列表类型
     * @return
     */
    public static <T> List<T> select(String sql,Class<T> clazz, Object... params){
        List<T> list = new ArrayList<>();
        PreparedStatement statement;
        try {
            statement = connection.prepareStatement(sql);
            for(int i=1; i<= params.length; i++){
                statement.setObject(i, params[i-1]);
            }
            ResultSet rs = statement.executeQuery();
            while(rs.next()){
                T t = clazz.newInstance();
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(method.getName().startsWith("set")){
                        String field = BeansInitUtil.firstToLowerCase(method.getName().substring(3));
                        method.invoke(t, rs.getObject(field));
                    }
                }
                list.add(t);
            }
        } catch (Exception e) {
            log.error("select exception.", e);
        }
        return list.size()>0 ? list : null;
    }
}

init方法用来初始化链接池,update方法执行insert/update/delete语句,select处理查询并简单使用反射封装了一下Entity。

04、添加JdbcTemplate

JdbcTemplate类,只是封装了一下DbUtil的方法,提供给其它service使用,代码如下:

package com.jisuye.core;
//import ...
/**
 * 添加数据库支持(默认不加载该Bean,有其它Bean引用时再加载)
 * @author ixx
 * @date 2019-07-05
 */
public class JdbcTemplate {

    public int insert(String sql){
        return DbUtil.update(sql) ? 1 : 0;
    }
    public int insert(String sql, Object... params){
        return DbUtil.update(sql, params) ? 1 : 0;
    }

    public int update(String sql, Object... params){
        return DbUtil.update(sql, params) ? 1 : 0;
    }
    public int update(String sql){
        return DbUtil.update(sql) ? 1 : 0;
    }

    public int delete(String sql){
        return DbUtil.update(sql) ? 1 : 0;
    }
    public int delete(String sql, Object... params){
        return DbUtil.update(sql, params) ? 1 : 0;
    }

    public <T> List<T> select(String sql,Class<T> clazz, Object... param){
        return DbUtil.select(sql, clazz, param);
    }
}

修改依赖注入部分代码,因为做的是框架,要考虑不使用DB的情况,所以jdbcTemplate默认不初始化,如果有其它Service添加了JdbcTemplate的依赖,再去初始化。防止直接初始化时,没有配置DB相关配置报错的情况。

BeansInitUtil.initDI()方法做如下修改(只保留了大体结构,具体查看=======标记中间的部分):

private static void initDI(Map<String, BeanObject> map){
        List<Object> beanList = new ArrayList<>();
        BeanObject sqlBean = null;
        // 循环所有Bean处理依赖
        for(Map.Entry entry : map.entrySet()){
            // ...
            // 先判断是否有Resource注解
            for (Field field : beanObject.getFields()) {
                if(filterFieldAnnotation(field.getAnnotations())){
                    String name = getResourceName(field.getAnnotations());
                    BeanObject bean = null;
                    // 有指定bean名字按指定去取
                    if(name != null && !name.equals("")){
                        bean = map.get(firstToLowerCase(name));
                    } else {
                        // ...
                        // 如果有next说明是有多个实现的接口,则要判断名字
                        if(bean != null && bean.getNext() != null){
                            // ...=============================注意下边这部分=============
                        } else if(fieldClass.getName().equals(JdbcTemplate.class.getName())){
                            // 如果是JdbcTemplate依赖,则初始化DbUtil并初始化及注入JdbcTemplate
                            if(sqlBean == null) {
                                DbUtil.init();
                                sqlBean = new BeanObject();
                                sqlBean.setClass(JdbcTemplate.class);
                                sqlBean.setObject(new JdbcTemplate());
                            }
                            bean = sqlBean;
                        }
                        // ...=============================注意上边这部分=============
                    }
                    if(bean == null){
                        // ...
                    }
                    // 注入依赖
                    // ...
                }
            }
        }
        map.put(JdbcTemplate.class.getName(), sqlBean);
    }

05、验证数据库支持

表abc的表结构:

字段名 字段类型 备注
id int 自增id主健
name varchar(255) 姓名

修改DefImpl.java,添加JdbcTemplate操作:

@Service
public class DefImpl implements Def {
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public String exe(String name) {
        List<AbcEntity> list = jdbcTemplate.select("select * from abc where name=?", AbcEntity.class, name);
        System.out.println(list.size());
        System.out.println(list.get(0).getId());
        System.out.println(list.get(0).getName());
        return "Interface DI... "+name;
    }
}

修改AbcImpl.java, 添加JdbcTemplate操作:

@Service
public class AbcImpl implements Abc {
    // 名字对不上会报异常
    @Resource
    private Def defImpl;
    // 名字对不上可以使用注解中指定bean名字的方式
    @Resource(name = "def2Impl")
    private Def defByName;
    // 添加jdbcTemplate依赖
    @Resource
    private JdbcTemplate jdbcTemplate;
    // 注入Class类实例
    @Resource
    private ClassDi classDi;
    @Override
    public int test(String name) {
        jdbcTemplate.insert("insert into abc(`name`) values('ixx')");
        System.out.println(defImpl.exe(name));
        System.out.println(defByName.exe(name));
        System.out.println(classDi.exe(name));
        return 0;
    }
}

添加AbcEntity.java,查询结果使用

package com.jisuye.service;

public class AbcEntity {
    private Integer id;
    private String name;
    // getter and setter...
}

原来的SquareApplication中有查看bean是否注入成功片段, 所以直接启动项目即可验证。

public static void run(Class clzz, String[] args) {
        try {
            // ...
            //查看bean是否注入成功
            Abc abc = (Abc)(ApplicationContext.getBean("abcImpl").getObject());
            abc.test("ixx");
            // ...
        } catch (Exception e){
            // ...
        }
}

查看控制台输出:

18:18:39.761 [main] INFO com.jisuye.core.SquareApplication - beans size is:11
1
1
ixx
Interface DI... ixx
def2 ixx
Class DI ixx

查看数据库记录:

id name
1 ixx

说明插入及查询成功。

06、翻车时间

数据库这还好,虽然没有打包在其实项目引用测试,这个等下一篇添加完web支持后一起验证。

发现一个注入的问题,当时处理注入是循环的整个容器里的bean去做解析,但很明显,容器中的bean是多对一的关系,多个key对应的 都是同一个Bean 如果使用循环的方式,就至少会多一倍解析处理..这个确实不好接受,Bean的数量少还可以,多了会延长程序的启动时间,这是不能接受的。解决方式其实也简单,下一篇再处理一下吧。

本篇代码地址: https://github.com/iuv/square/tree/square5

本文作者: ixx
本文链接: http://jianpage.com/2019/07/08/square5
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

推荐阅读更多精彩内容