Spring-JDBC-基础

JDBC概述

JDBC(java DataBase Connectivity):java数据库连接。是一种用于执行sql语句的Java API。JDBC是Java访问数据库的标准规范。可以为不同的关系型数据库提供统一的访问,它由一组用Java语言编写的接口和类组成。

驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。

我们已经知道JDBC是访问数据库的规范。那么生产厂家提供规范的实现类称为驱动
在这里插入图片描述

原生JDBC访问数据库

项目要使用数据库。首先要导入对应的依赖(驱动)。以mysql为例:

 <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

1.初始化驱动

Class.forName("com.mysql.jdbc,Driver")

目的就是把Driver.class文件加载到内存。
1.JVM会加载这个类

2.加载这个类后,进入com.mysql.jdbc.Driver,会执行里面的静态代码块:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

将驱动类中的Driver注册到DriverManager类中。

3…进入java.sql.DriverManager,DriverManager管理器会调用注册方法,并把Driver放入registeredDrivers列表中:

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

4.调用DriverManager.getConnection获取连接时:

 @CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        return (getConnection(url, info, Reflection.getCallerClass()));
    }

5.接着看getConnection

 //  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized (DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }
}

核心逻辑就是:
  校验集合registeredDrivers 中的驱动是否存在。存在执行下面逻辑。
  从集合registeredDrivers 中取出DriverInfo(Driver的包装),通过调用 Connection con = aDriver.driver.connect(url, info);得到连接对象。也就是调用驱动Driver中的connect方法获取连接。

2.建立连接

Connection conn = DriverManager.getConnection(url,username,password)

根据数据库的连接地址,用户名,密码。与数据库建立连接。

3.获取SQL语句执行对象

Statement pt= conn.createStatement();
String sql = “sql语句”

在这里插入图片描述
Statement:可以发送字符串类型的sql,不支持传递参数。适用于静态sql语句。
PreparedStatement: 预编译的sql语句,接受参数传入,并且可以防止sql注入,提高安全性。Sql语句会编译在数据库系统,适用于多次执行相同的sql,因此性能高于Statement。
CallableStatement:在执行存储过程时使用,并且可以接受参数传入。

4.执行SQL语句

增删改:(int)pt.excuteUpdate(sql )
查: (ResultSet)pt.excuteQuery()

5.关闭连接

ResultSet.close();
pt.close();
conn.close();

Spring-jdbc

Spring框架封装了一个数据源的模型类:DriverManagerDataSource。通过这个可以构造数据源模型,用于访问数据源。一般需要设置以下几个属性:
1.driverClassName:驱动类型
2.url:数据源连接地址
3.username:连接用户名
4.password:连接密码

DriverManagerDataSource 的底层就是 DriverManager.getConnection() 的操作。

xml中配置jdbcTemplate

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
</beans>

然后就可以使用jdbcTemplate进行sql操作。
增加:

int row = jdbcTemplate.update("insert into user_info(name, tel) values (?, ?)", "heihei", "200");

更改:

int row = jdbcTemplate.update("update user_info set tel = ? where name = ?", "54321", "heihei");

删除:

int row = jdbcTemplate.update("delete from  user_info where name = ?", "heihei");

查询:
1.将结果封装为User类型。

List<User> userList = jdbcTemplate.query("select * from  user_info", new BeanPropertyRowMapper<>(User.class));

2.也可以直接指定返回的类型:queryForObject 。这个 SQL 返回的必定是一个只有一行一列的数据

   Long count = jdbcTemplate.queryForObject("select count(id) from user_info ", Long.class);

3.根据传入的类型,返回结果集合。只能是查询结果只有一列时,才可以指定的:

List<String> userList = jdbcTemplate.queryForList("select name from tbl_user", String.class);

4.不指定唇乳的类型,返回结果用Map封装

List<Map<String,Object>> userList = jdbcTemplate.queryForList("select name from tbl_user");

JdbcTemplate的扩展NamedParameterJdbcTemplate

使用jdbcTemplate:

jdbcTemplate.queryForList("select * from user_info where id > ? and name like ?", 3, "ha%");

如上,这条 SQL 有两个参数,后面传入的参数必须要保证顺序与 SQL 一致。

使用NamedParameterJdbcTemplate:

select * from  user_info where id > :id and name like :name

总结:
JdbcTemplate 是靠 ? 占位符 + 可变参数。
NamedParameterJdbcTemplate 的套路是靠参数变量名和 Map 设置了。

Map<String, Object> params = new HashMap<>();
    params.put("id", 1);
    params.put("name", "xiao%");
    List<Map<String, Object>> userList = jdbcTemplate
        .queryForList("select * from user_info where id > :id and name like :name", params);

大文本的处理

大字段的核心 API 是一个 LobHandlerLobCreator
在配置类中新一个@Bean。LobHandler

   @Bean
    public LobHandler lobHandler() {
        return new DefaultLobHandler();
    }

在业务中,可以通过lobHandler的相关方法获取大字段。

JDBC事务

事务有四大特性:

1.原则性:一个事务就是一个不可分割的单位。一个事务内的操作,要么全部做,要么全部不做。

2.一致性:事务执行后,所有的数据都应该保持一致性。

3.隔离性:多个数据库操作并发执行时,一个请求的事务操作不能被其他操作干扰,多个并发事务执行之间要相互隔离。隔离性强调的是并发的隔离。

4.持久性:事务执行后,它对数据的影响是永久性的。

并发事务操作出现的问题:

1.脏读:一个事务读取到了另一个事务没有提交的数据。

2.不可重复读:一个事务读取到了另一个事务已经修改提交的数据。对同一行数据查询两次,结果不一致。

3.幻读:一个事务读取到另一个事务已经新增的数据。对同一张表查询两次,出现新增的行,导致结果不一致。

隔离级别

针对并发事务出现的问题。引入了事务的隔离级别:
1.read uncommitted:读未提交—不解决任何问题

2.read committed:读已提交—解决脏读

3.repeatable read:可重复度—解决脏读,不可重复读

4.serializable:可串行化—解决脏读,不可重复读,幻读

MySQL中默认的事务隔离级别是:repeatable read。
Oracle默认的事务隔离级别是read committed。

事务的保存点

一些场景,可能需要分段执行,如果出现异常不需要全部回滚。这时候就需要用到保存点。
1.通过数据源获取到数据库连接对象Connection

2.通过连接对象设置保存点
savepoint = Connection.setSavepoint();

3.异常的时候回退到保存点
Connection.rollback(savepoint)
Connection.commit();//提交保存点之前的事务

Spring框架中引入编程式事务

1.配置事务相关的组件

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

DataSourceTransactionManager:事务管理器,负责控制事务。
TransactionTemplate:事务模板,使用它可以完成编程式事务。

2.在业务中可以直接注入transactionTemplate
在业务中直接使用 transactionTemplate.execute方法,这个方法需要传入一个TransactionCallback 类型的对象。TransactionCallback 是一个函数式接口:

@FunctionalInterface
public interface TransactionCallback<T> {
	T doInTransaction(TransactionStatus status);
}

代码中就可以这样使用:

 transactionTemplate.execute(status -> {
        userDao.save(user);

        int i = 1 / 0;

        List<User> userList = userDao.findAll();
        System.out.println(userList);
        //返回execute方法的执行结果
        return null;
    });

Spring框架中提供了一个:TransactionCallbackWithoutResult 。就是用来封装返回null的操作。然后就可以直接用这个类。

 transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            userDao.save(user);

            int i = 1 / 0;

            List<User> userList = userDao.findAll();
            System.out.println(userList);
        }
    });

DataSourceTransactionManager

它是基于数据源的事务管理器。它实现的根接口PlatformTransactionManager 定义了commit和rollback方法:

public interface PlatformTransactionManager extends TransactionManager {
	TransactionStatus getTransaction(TransactionDefinition definition)
			throws TransactionException;
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;
}

这个 commit 和 rollback 方法要传入一个 TransactionStatus 的参数。

TransactionTemplate

这个和JdbcTemplate是类似的。都是提供一个模板来完成平时比较复杂的工具。
它的核心方法是来自 TransactionOperations 接口定义的 execute 方法:

@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
    }
    else {
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        try {
            result = action.doInTransaction(status);
        }
        catch (RuntimeException | Error ex) {
            // 业务代码出现异常,回滚事务
            rollbackOnException(status, ex);
            throw ex;
        }
        catch (Throwable ex) {
            // 业务代码出现异常,回滚事务
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        // try块没有出现异常,业务代码执行成功,提交事务
        this.transactionManager.commit(status);
        return result;
    }
}

它帮我们完成事务的相关操作。

Spring框架中声明式事务

xml配置声明式事务

对于 xml 配置文件的声明式事务,需要引入新的命名空间了:tx ,它就是关于事务的配置部分。

引入依赖:Aop整合AspectJ

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
<!-- transactionManager为配置的事务管理器id -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 配置事务的拦截方法,可以使用通配符 -->
        <tx:method name="save*"/><!-- 所有save开头的方法全部开启事务控制 -->
    </tx:attributes>
</tx:advice>

AOP配置中引入这个通知:

<aop:config>
    <aop:advisor advice-ref="transactionAdvice"
                 pointcut="execution(* com.test.transaction.xmlTest.service.*.*(..))"/>
</aop:config>

tx:method的其它属性
1.isolation :事务隔离级别。默认是 DEFAULT ,即依据数据库默认的事务隔离级别来定。

2.timeout :事务超时时间,当事务执行超过指定时间后,事务会自动中止并回滚,单位 秒 。默认值 -1 ,代表永不超时。

3.read-only :设置是否为只读事务。默认值 false ,代表读写型事务。
当设置为 true 时,当前事务为只读事务,通常用于查询操作(此时不会有 setAutoCommit(false) 等操作,可以加快查询速度)。

4.rollback-for :当方法触发指定异常时,事务回滚,需要传入异常类的全限定名。
默认值为空,代表捕捉所有 RuntimeException 和 Error 的子类。
一般情况下,在日常开发中,我们都会显式声明其为 Exception ,目的是一起捕捉非运行时异常。

5.no-rollback-for :当方法触发指定异常时,事务不回滚继续执行,需要传入异常类的全限定名。默认值为空,代表不忽略异常。

6.propagation :事务传播行为。

基于注解驱动的声明式事务

配置类上开启事务–@EnableTransationManagement

@Configuration
@ComponentScan("com.linkedbear.spring.transaction.d_declarativeanno")
@EnableTransactionManagement
public class DeclarativeTransactionConfiguration {

业务代码中通过@Transational开启注解
@Transational的其他属性和xml中配置的几乎一样

@Transactional
public void save() {
    userDao.save(user);
}

也可以通过xml配置,开启注解声明事务

<tx:annotation-driven transaction-manager="transactionManager"/>

Spring框架中事务传播行为

事务与事务之间,如何决定事务的行为?

事务的传播行为:外层的事务传播到内层事务中,内层事务作出的行为。这就是事务传播行为。

Spring框架定义了7中事务传播行为:
1.REQUIRED:必须的【默认值】
含义:如果当前没有事务运行,则会开启一个新的事务。如果当前已经有事务,则方法会运行在当前事务中。

2.REQUIRES_NEW:新事物
含义:如果当前没有事务运行,则会开启一个新的事务。如果当前已经有事务运行,则会将当前事务挂起,重新开启一个新的事务。当新的事务运行完毕,再将原来的事务释放。

3.SUPPORTS:支持
含义:如果当前有事务运行,则方法会运行在当前事务中。如果当前没有事务运行,则不会创建新的事务。

4.NOT_SUPPORTED:不支持
如果当前有事务运行,则会将该事务挂起。如果当前没有事务运行,则它也不会运行在事务中。

5.MANDATORY:强制
当前方法必须运行在事务中,如果没有事务,则直接抛出异常。

6.NEVER:不允许
当前方法不允许运行在事务中,如果当前已经有事务运行,则抛出异常。

7.NESTED:嵌套
如果当前没有事务运行,则开启一个新的事务;如果当前已经有事务运行,则会记录一个保存点,并继续运行在当前事务中。如果子事务运行中出现异常,则不会全部回滚,而是回滚到上一个保存点。

在xml中配置传播行为

<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- propagation属性中填写事务的传播行为 -->
        <tx:method name="register" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

注解配置传播行为

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testAnnotationTransation() {
    System.out.println("testAnnotationTransation......");
}

运行事务确定
要想知道运行的是哪个事务。通过TransactionSynchronizationManager
在方法内,通过TransactionSynchronizationManager.getCurrentTransactionName()。就可以获取当前事务的名称(事务的名称就是开启事务时触发的方法)。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值