造一个方形的轮子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 国际许可协议 进行许可。转载请注明出处!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容