JDBC(java DataBase Connectivity):java数据库连接。是一种用于执行sql语句的Java API。JDBC是Java访问数据库的标准规范。可以为不同的关系型数据库提供统一的访问
,它由一组用Java语言编写的接口和类组成。
驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。
我们已经知道JDBC是访问数据库的规范。那么生产厂家提供规范的实现类称为驱动。
项目要使用数据库。首先要导入对应的依赖(驱动)。以mysql为例:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
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方法获取连接。
Connection conn = DriverManager.getConnection(url,username,password)
根据数据库的连接地址,用户名,密码。与数据库建立连接。
Statement pt= conn.createStatement();
String sql = “sql语句”
Statement:可以发送字符串类型的sql,不支持传递参数。适用于静态sql语句。
PreparedStatement: 预编译的sql语句,接受参数传入,并且可以防止sql注入,提高安全性。Sql语句会编译在数据库系统,适用于多次执行相同的sql,因此性能高于Statement。
CallableStatement:在执行存储过程时使用,并且可以接受参数传入。
增删改:(int)pt.excuteUpdate(sql )
查: (ResultSet)pt.excuteQuery()
ResultSet.close();
pt.close();
conn.close();
Spring框架封装了一个数据源的模型类:DriverManagerDataSource。通过这个可以构造数据源模型,用于访问数据源。一般需要设置以下几个属性:
1.driverClassName:驱动类型
2.url:数据源连接地址
3.username:连接用户名
4.password:连接密码
DriverManagerDataSource 的底层就是 DriverManager.getConnection() 的操作。
xml中配置jdbcTemplate
<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:
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 是一个 LobHandler
和 LobCreator
。
在配置类中新一个@Bean。LobHandler
@Bean
public LobHandler lobHandler() {
return new DefaultLobHandler();
}
在业务中,可以通过lobHandler的相关方法获取大字段。
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();//提交保存点之前的事务
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);
}
});
它是基于数据源的事务管理器。它实现的根接口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
的参数。
这个和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;
}
}
它帮我们完成事务的相关操作。
对于 xml 配置文件的声明式事务,需要引入新的命名空间了:tx
,它就是关于事务的配置部分。
引入依赖:Aop整合AspectJ
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
dependency>
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="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框架定义了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>
<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()
。就可以获取当前事务的名称(事务的名称就是开启事务时触发的方法)。