012-Spring DAO 数据访问对象

数据源

JDBC数据源DriverManagerDataSource

主要参数如下:

  • jdbcDriver:jdbc驱动
  • url:数据库连接
  • username:用户名
  • password:密码

DBCP数据源BasicDataSource

该数据源依赖于 commons-dbcp.jar, commons-pool对象池机制的数据库连接池。BasicDataSource提供了close()方法关闭数据源,所以必须设定destroy-method=”close”属性, 以便Spring容器关闭时,数据源能够正常关闭。除以上必须的数据源属性外,还有一些常用的属性。

  • defaultAutoCommit:设置从数据源中返回的连接是否采用自动提交机制,默认值为 true
  • defaultReadOnly:设置数据源是否仅能执行只读操作, 默认值为 false
  • maxActive:最大连接数据库连接数,设置为0时,表示没有限制
  • maxIdle:最大等待连接中的数量,设置为0时,表示没有限制
  • maxWait:最大等待秒数,单位为毫秒, 超过时间会报出错误信息
  • validationQuery:用于验证连接是否成功的查询SQL语句,SQL语句必须至少要返回一行数据, 如你可以简单地设置为:“select count(*) from user”
  • removeAbandoned:是否自我中断,默认是 false
  • removeAbandonedTimeout:几秒后数据连接会自动断开,在removeAbandoned为true,提供该值
  • logAbandoned:是否记录中断事件, 默认为 false

C3P0数据源ComboPooledDataSource

C3P0是一个开放源代码的JDBC数据源实现项目,它在lib目录中与Hibernate一起发布,实现了JDBC3和JDBC2扩展规范说明的 Connection 和Statement 池。C3P0类包c3p0-0.9.0.4.jar。C3P0拥有比DBCP更丰富的配置属性,通过这些属性,可以对数据源进行各种有效的控制。

HikariCP

官网:https://www.worldlink.com.cn/zh_tw/osdir/hikaricp.html
HikariCP代码非常轻量,并且速度非常的快。 HikariCP在 spring-boot-starter-jdbc 中已经被引入,意味着它是spring默认推荐的数据源。

HikariConfig配置

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
config.setUsername("bart");
config.setPassword("51mp50n");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

HikariDataSource ds = new HikariDataSource(config)

HikariDataSource直接配置

HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
ds.setUsername("bart");
ds.setPassword("51mp50n");

使用配置文件

HikariConfig config = new HikariConfig("/some/path/hikari.properties");
HikariDataSource ds = new HikariDataSource(config);

使用java.util.Properties

Properties props = new Properties();
props.setProperty("dataSourceClassName", "org.postgresql.ds.PGSimpleDataSource");
props.setProperty("dataSource.user", "test");
props.setProperty("dataSource.password", "test");
props.setProperty("dataSource.databaseName", "mydb");
props.put("dataSource.logWriter", new PrintWriter(System.out));

HikariConfig config = new HikariConfig(props);
HikariDataSource ds = new HikariDataSource(config);

HikariCP配置说明

必要的属性
属性说明默认值
dataSourceClassNameJDBC驱动程序提供的数据源类的名称。注意:不支持XA数据源。如果使用jdbcUrl配置,则不需配置此属性null
jdbcUrljdbc链接urlnull
username数据库用户名null
password数据库密码null
driverClassName数据库驱动null
常用属性
属性说明默认值
autoCommit控制从池返回的连接的默认自动提交行为true
connectionTimeout连接超时时间,超出此时间将抛出SQLException。支持最低250ms默认值:30000(30秒)
idleTimeout空闲连接存活的最长时间。只在minimumIdle < maximumPoolSize时有效。即当前连接数小于minimumIdle 时存活的连接不会失效。最小允许值为10000ms(10秒)。0表示不失效默认值:600000(10分钟)
maxLifetime连接的最大生存期。正在使用的连接不会失效。它也遵从idleTimeout 的设置默认值:1800000(30分钟)
connectionTestQuery用于检查连接是否有效的sql查询语句(例如:select 1 from dual)。适用于JDBC4以前的驱动程序。建议您先不要设置,如果你的驱动程序不是JDBC4,HikariCP会报错,否则会正常运行。
minimumIdle允许的空闲连接的最小数量默认值:与maximumPoolSize相同(注意:最好是小于最大值)
maximumPoolSize允许的连接数最大值(包括空闲连接+正在使用的连接)。当连接数到达该数量,则等待connectionTimeout毫秒,超出则抛出异常。默认值:10
poolName此属性表示连接池的用户定义名称,主要出现在日志和JMX管理控制台中,用于标识池和池配置。默认值:自动生成
不常用属性

还有很多不常用属性,请自行查询官网把。

Druid

Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。
Github项目地址 https://github.com/alibaba/druid
文档 https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98
下载 http://repo1.maven.org/maven2/com/alibaba/druid/
监控DEMO http://120.26.192.168/druid/index.html

Druid对比HikariCP

HikariCP简洁,速度快,Druid功能强大,二者侧重点不同,如果你不需要过多的功能,只专注于提供连接池的话,推荐使用HikariCP。

友情提示:使用Druid如果不使用它的监控功能,请做如下配置

# 禁用druid监控,如果启用,请设置用户名密码(否则会有未授权的漏洞),默认是true
spring.datasource.druid.stat-view-servlet.enabled=false

Spring的DAO抽象

  • JdbcDaoSupport:JDBC DAO抽象类。开发者需要为它设置数据源(DataSource),通过其子类,可以获得JdbcTemplate来访问数据库。
  • HibernateDaoSupport:Hibernate DAO抽象类。开发者需要配置Hibernate sessionFactory,通过其子类,可以获得HibernateTemplate来访问数据库。HibernateTemplate中有execute和executeFind两个方法接受一个HibernateCallback接口回调,在该接口中的doInHibernate方法中可以直接使用Session,以及其涉及到的HibernateAPI如Query对象等对象。
  • JdoDaoSupport:String为JDO提供的DAO抽象类,开发者需要为它配置PersistenceManagerFactory,通过其子类,可以获得JdoTemplate来访问数据库。

JdbcTemplate + HikariCP数据源示例

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yyoo.mytest</groupId>
    <artifactId>springboot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <!-- spring-boot-starter-parent是一个特殊的启动器,他可以使我们在下面定义jar依赖时,不用提供版本标签 version -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
    </parent>

    <dependencies>
        <!-- 添加web依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring jdbc相关依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

可以看到与我们之前的依赖相比,就只多了spring-boot-starter-jdbc依赖和mysql的驱动依赖

Properties文件

jdbcUrl=jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username=用户名
password=密码
driverClassName=com.mysql.cj.jdbc.Driver

中间汉字部分请自行替换

创建数据库表

我们使用mysql创建如下两张表,用于示例展示
在这里插入图片描述
表名:t_test,有3个字段。
在这里插入图片描述
表名:t_demo,有3个字段

配置HikariCP数据源

package com.yyoo.boot.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
@ComponentScan("com.yyoo.boot.dao")
public class DaoConfig {

    @Value("classpath:dao.properties")
    private Resource configResource;

    @Bean
    public DataSource getDriverManagerDataSource() throws IOException {
		//-- 此处使用了Resource ,请查看Resource章节
        String propertiesPath = configResource.getFile().getAbsolutePath();
        HikariConfig hikariConfig = new HikariConfig(propertiesPath);

        HikariDataSource ds = new HikariDataSource(hikariConfig);

        return ds;
    }

}

测试代码(先验证数据源)

package com.yyoo.boot.dao;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;

public class Demo1 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(DaoConfig.class);
        context.refresh();
        context.registerShutdownHook();

        DataSource dataSource = context.getBean(DataSource.class);

        System.out.println(dataSource);

    }

}

建议在架构搭建的过程中进行分段验证,避免一次写完所有配置,再执行。否则如果出现错误或问题,你的架构代码半天也启动不起来。

注意:context.registerShutdownHook();的作用,如果没有该语句,spring在程序执行完成(容器关闭时)不会回调关闭相应的资源(如:连接池的释放等)。所以context.registerShutdownHook();还是十分有必要的。

配置JdbcTemplate

在配置类中添加JdbcTemplate的配置

package com.yyoo.boot.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
@ComponentScan("com.yyoo.boot.dao")
public class DaoConfig {

    @Value("classpath:dao.properties")
    private Resource configResource;

    @Bean
    public DataSource getDriverManagerDataSource() throws IOException {

        String propertiesPath = configResource.getFile().getAbsolutePath();
        HikariConfig hikariConfig = new HikariConfig(propertiesPath);

        HikariDataSource ds = new HikariDataSource(hikariConfig);

        return ds;
    }


    /**
     *
     * 因为JdbcTemplate一旦创建就是线程安全的,所以我们可以将它定义为一个单例的bean
     *
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }


}

JdbcTemplate用了ThreadLocal,使各线程能够保持各自独立的一个对象,其实就是一个变量副本,实现了线程安全。 JdbcTemplate类的实例是线程安全的实例。这一点非常重要,正因为如此,你可以配置一个简单的JdbcTemplate实例,并将这个“共享的”、“安全的”实例注入到不同的DAO类中去。 另外, JdbcTemplate 是有状态的,因为他所维护的DataSource 实例是有状态的,但是这种状态是无法变化的。 一旦JdbcTemplate被创建,他是一个线程安全的对象。 一个你需要创建多次JdbcTemplate实例的理由可能在于,你的应用需要访问多个不同的数据库,从而需要不同的DataSources来创建不同的JdbcTemplates实例。

编写dao类

TestDao和TestDaoImpl

package com.yyoo.boot.dao.dao;

import com.yyoo.boot.dao.beans.TestBean;

public interface TestDao {

    int insert(TestBean bean);

    int update(TestBean bean);

    TestBean getTestBeanById(int id);

}

package com.yyoo.boot.dao.dao;

import com.yyoo.boot.dao.beans.TestBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository
public class TestDaoImpl implements TestDao {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public int insert(TestBean bean) {
        return jdbcTemplate.update("insert into t_test(id,name,remark) values(?,?,?)",
                bean.getId(),bean.getName(),bean.getRemark());
    }

    @Override
    public int update(TestBean bean) {
        return jdbcTemplate.update("update set t_test name = ?,remark=? where id = ?",
                bean.getId(),bean.getName(),bean.getRemark());
    }

    @Override
    public TestBean getTestBeanById(int id) {
        return jdbcTemplate.queryForObject("select id,name,remark from t_test where id = "+id,TestBean.class);
    }
}

DemoDao和DemoDaoImpl

package com.yyoo.boot.dao.dao;

import com.yyoo.boot.dao.beans.DemoBean;

public interface DemoDao {

    int insert(DemoBean bean);

    int update(DemoBean bean);

    DemoBean getTestBeanById(int id);

}
package com.yyoo.boot.dao.dao;

import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository
public class DemoDaoImpl implements DemoDao {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public int insert(DemoBean bean) {
        return jdbcTemplate.update("insert into t_demo(id,`key`,`value`) values(?,?,?)",
                bean.getId(),bean.getKey(),bean.getValue());
    }

    @Override
    public int update(DemoBean bean) {
        return jdbcTemplate.update("update set t_demo `key` = ?,`value`=? where id = ?",
                bean.getId(),bean.getKey(),bean.getValue());
    }

    @Override
    public DemoBean getTestBeanById(int id) {
        return jdbcTemplate.queryForObject("select id,`key`,`value` from t_demo where id = "+id, DemoBean.class);
    }
}

注:

  1. key、value是保留关键字,我们的sql字符串使用了反引号,如果不使用JdbcTemplate会报错。
  2. 我们看到两个Dao实现类都使用了 @Resource 注入了一个JdbcTemplate引用。其实我们可以定义一个BaseDao和BaseDaoImpl,在BaseDaoImpl中注入一次JdbcTemplate引用即可,其他的DaoImpl继承该BaseDaoImpl即可。

定义一个测试Service

package com.yyoo.boot.dao.service;

import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;

public interface MyService {

    void insert(TestBean test, DemoBean demo);

}

package com.yyoo.boot.dao.service;

import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;
import com.yyoo.boot.dao.dao.DemoDao;
import com.yyoo.boot.dao.dao.TestDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class MyServiceImpl implements MyService {

    @Resource
    private TestDao test1Dao;
    @Resource
    private DemoDao demoDao;


    @Override
    public void insert(TestBean test, DemoBean demo) {
        test1Dao.insert(test);

        demoDao.insert(demo);
    }
}

我们此处只定义了一个insert方法,方法中同事insert了两个表,个一条数据。如要验证其他Dao方法,请自己补全把。

测试demo

package com.yyoo.boot.dao;

import com.yyoo.boot.dao.beans.DemoBean;
import com.yyoo.boot.dao.beans.TestBean;
import com.yyoo.boot.dao.service.MyService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;

public class Demo1 {

    @Test
    public void test(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(DaoConfig.class);
        context.refresh();
        context.registerShutdownHook();

        DataSource dataSource = context.getBean(DataSource.class);

        System.out.println(dataSource);


        MyService service = context.getBean(MyService.class);
        TestBean bean = new TestBean();
        bean.setId(1);
        bean.setName("测试名称");
        bean.setRemark("测试备注");

        DemoBean demo = new DemoBean();
        demo.setId(1);
        demo.setKey("demokey");
        demo.setValue("demovalue");

        service.insert(bean,demo);

    }

}

完整的代码结构

在这里插入图片描述

示例Service中,如果 test1Dao.insert(test); 之后demoDao.insert(demo);之前有其它业务逻辑代码,而且在这些逻辑代码中出现了异常,我们目前的代码会出现t_test表插入成功而t_demo表插入失败的问题。这显然是不符合业务逻辑的。我们将在下一章介绍此涉及到的事务问题。

上一篇:011-Spring AOP入门
下一篇:013-Spring事务管理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值