spring事务中查询主库还是从库

spring事务中查询主库还是从库,第1张

可以给所有读的方法添加一个参数,来控制读从库还是主库。

2、数据源如何路由?

spring-jdbc 包中提供了一个抽象类:AbstractRoutingDataSource,实现了javaxsqlDataSource接口,我们用这个类来作为数据源类,重点是这个类可以用来做数据源的路由,可以在其内部配置多个真实的数据源,最终用哪个数据源,由开发者来决定。

AbstractRoutingDataSource中有个map,用来存储多个目标数据源

private Map<Object, DataSource> resolvedDataSources;

比如主从库可以这么存储

resolvedDataSourcesput("master",主库数据源);

resolvedDataSourcesput("salave",从库数据源);

AbstractRoutingDataSource中还有抽象方法determineCurrentLookupKey,将这个方法的返回值作为key到上面的resolvedDataSources中查找对应的数据源,作为当前 *** 作db的数据源

protected abstract Object determineCurrentLookupKey();

3、读写分离在哪控制?

读写分离属于一个通用的功能,可以通过spring的aop来实现,添加一个拦截器,拦截目标方法的之前,在目标方法执行之前,获取一下当前需要走哪个库,将这个标志存储在ThreadLocal中,将这个标志作为AbstractRoutingDataSourcedetermineCurrentLookupKey()方法的返回值,拦截器中在目标方法执行完毕之后,将这个标志从ThreadLocal中清除。

3、代码实现

31、工程结构图

32、DsType

表示数据源类型,有2个值,用来区分是主库还是从库。

package comjavacode2018readwritesplitbase;

public enum DsType {

MASTER, SLAVE;

}

33、DsTypeHolder

内部有个ThreadLocal,用来记录当前走主库还是从库,将这个标志放在dsTypeThreadLocal中

package comjavacode2018readwritesplitbase;

public class DsTypeHolder {

private static ThreadLocal<DsType> dsTypeThreadLocal = new ThreadLocal<>();

public static void master() {

dsTypeThreadLocalset(DsTypeMASTER);

}

public static void slave() {

dsTypeThreadLocalset(DsTypeSLAVE);

}

public static DsType getDsType() {

return dsTypeThreadLocalget();

}

public static void clearDsType() {

dsTypeThreadLocalremove();

}

}

34、IService接口

这个接口起到标志的作用,当某个类需要启用读写分离的时候,需要实现这个接口,实现这个接口的类都会被读写分离拦截器拦截。

package comjavacode2018readwritesplitbase;

//需要实现读写分离的service需要实现该接口

public interface IService {

}

35、ReadWriteDataSource

读写分离数据源,继承ReadWriteDataSource,注意其内部的determineCurrentLookupKey方法,从上面的ThreadLocal中获取当前需要走主库还是从库的标志。

package comjavacode2018readwritesplitbase;

import orgspringframeworkjdbcdatasourcelookupAbstractRoutingDataSource;

import orgspringframeworklangNullable;

public class ReadWriteDataSource extends AbstractRoutingDataSource {

@Nullable

@Override

protected Object determineCurrentLookupKey() {

return DsTypeHoldergetDsType();

}

}

36、ReadWriteInterceptor

读写分离拦截器,需放在事务拦截器前面执行,通过@1代码我们将此拦截器的顺序设置为IntegerMAX_VALUE - 2,稍后我们将事务拦截器的顺序设置为IntegerMAX_VALUE - 1,事务拦截器的执行顺序是从小到达的,所以,ReadWriteInterceptor会在事务拦截器orgspringframeworktransactioninterceptorTransactionInterceptor之前执行。

由于业务方法中存在相互调用的情况,比如service1m1中调用service2m2,而service2m2中调用了service2m3,我们只需要在m1方法执行之前,获取具体要用哪个数据源就可以了,所以下面代码中会在第一次进入这个拦截器的时候,记录一下走主库还是从库。

下面方法中会获取当前目标方法的最后一个参数,最后一个参数可以是DsType类型的,开发者可以通过这个参数来控制具体走主库还是从库。

package comjavacode2018readwritesplitbase;

import orgaspectjlangProceedingJoinPoint;

import orgaspectjlangannotationAround;

import orgaspectjlangannotationAspect;

import orgaspectjlangannotationPointcut;

import orgspringframeworkcoreannotationOrder;

import orgspringframeworkstereotypeComponent;

import javautilObjects;

@Aspect

@Order(IntegerMAX_VALUE - 2) //@1

@Component

public class ReadWriteInterceptor {

@Pointcut("target(IService)")

public void pointcut() {

}

//获取当前目标方法的最后一个参数

private Object getLastArgs(final ProceedingJoinPoint pjp) {

Object[] args = pjpgetArgs();

if (ObjectsnonNull(args) && argslength > 0) {

return args[argslength - 1];

} else {

return null;

}

}

@Around("pointcut()")

public Object around(final ProceedingJoinPoint pjp) throws Throwable {

//判断是否是第一次进来,用于处理事务嵌套

boolean isFirst = false;

try {

if (DsTypeHoldergetDsType() == null) {

isFirst = true;

}

if (isFirst) {

Object lastArgs = getLastArgs(pjp);

if (DsTypeSLAVEequals(lastArgs)) {

DsTypeHolderslave();

} else {

DsTypeHoldermaster();

}

}

return pjpproceed();

} finally {

//退出的时候,清理

if (isFirst) {

DsTypeHolderclearDsType();

}

}

}

}

37、ReadWriteConfiguration

spring配置类,作用

1、@3:用来将comjavacode2018readwritesplitbase包中的一些类注册到spring容器中,比如上面的拦截器ReadWriteInterceptor

2、@1:开启spring aop的功能

3、@2:开启spring自动管理事务的功能,@EnableTransactionManagement的order用来指定事务拦截器orgspringframeworktransactioninterceptorTransactionInterceptor顺序,在这里我们将order设置为IntegerMAX_VALUE - 1,而上面ReadWriteInterceptor的order是IntegerMAX_VALUE - 2,所以ReadWriteInterceptor会在事务拦截器之前执行。

package comjavacode2018readwritesplitbase;

import orgspringframeworkcontextannotationComponentScan;

import orgspringframeworkcontextannotationConfiguration;

import orgspringframeworkcontextannotationEnableAspectJAutoProxy;

import orgspringframeworktransactionannotationEnableTransactionManagement;

@Configuration

@EnableAspectJAutoProxy //@1

@EnableTransactionManagement(proxyTargetClass = true, order = IntegerMAX_VALUE - 1) //@2

@ComponentScan(basePackageClasses = IServiceclass) //@3

public class ReadWriteConfiguration {

}

38、@EnableReadWrite

这个注解用来开启读写分离的功能,@1通过@Import将ReadWriteConfiguration导入到spring容器了,这样就会自动启用读写分离的功能。业务中需要使用读写分离,只需要在spring配置类中加上@EnableReadWrite注解就可以了。

package comjavacode2018readwritesplitbase;

import orgspringframeworkcontextannotationImport;

import javalangannotation;

@Target(ElementTypeTYPE)

@Retention(RetentionPolicyRUNTIME)

@Documented

@Import(ReadWriteConfigurationclass) //@1

public @interface EnableReadWrite {

}

4、案例

读写分离的关键代码写完了,下面我们来上案例验证一下效果。

41、执行sql脚本

下面准备2个数据库:javacode2018_master(主库)、javacode2018_slave(从库)

2个库中都创建一个t_user表,分别插入了一条数据,稍后用这个数据来验证走的是主库还是从库。

DROP DATABASE IF EXISTS javacode2018_master;

CREATE DATABASE IF NOT EXISTS javacode2018_master;

USE javacode2018_master;

DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(256) NOT NULL DEFAULT ''

COMMENT '姓名'

);

INSERT INTO t_user (name) VALUE ('master库');

DROP DATABASE IF EXISTS javacode2018_slave;

CREATE DATABASE IF NOT EXISTS javacode2018_slave;

USE javacode2018_slave;

DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(256) NOT NULL DEFAULT ''

COMMENT '姓名'

);

INSERT INTO t_user (name) VALUE ('slave库');

42、spring配置类

@1:启用读写分离

masterDs()方法:定义主库数据源

slaveDs()方法:定义从库数据源

dataSource():定义读写分离路由数据源

后面还有2个方法用来定义JdbcTemplate和事务管理器,方法中都通过@Qualifier(“dataSource”)限定了注入的bean名称为dataSource:即注入了上面dataSource()返回的读写分离路由数据源。

package comjavacode2018readwritesplitdemo1;

import comjavacode2018readwritesplitbaseDsType;

import comjavacode2018readwritesplitbaseEnableReadWrite;

import comjavacode2018readwritesplitbaseReadWriteDataSource;

import orgspringframeworkbeansfactoryannotationQualifier;

import orgspringframeworkcontextannotationBean;

import orgspringframeworkcontextannotationComponentScan;

import orgspringframeworkcontextannotationConfiguration;

import orgspringframeworkjdbccoreJdbcTemplate;

import orgspringframeworkjdbcdatasourceDataSourceTransactionManager;

import orgspringframeworktransactionPlatformTransactionManager;

import javaxsqlDataSource;

import javautilHashMap;

import javautilMap;

@EnableReadWrite //@1

@Configuration

@ComponentScan

public class MainConfig {

//主库数据源

@Bean

public DataSource masterDs() {

orgapachetomcatjdbcpoolDataSource dataSource = new orgapachetomcatjdbcpoolDataSource();

dataSourcesetDriverClassName("commysqljdbcDriver");

dataSourcesetUrl("jdbc:mysql://localhost:3306/javacode2018_mastercharacterEncoding=UTF-8");

dataSourcesetUsername("root");

dataSourcesetPassword("root123");

dataSourcesetInitialSize(5);

return dataSource;

}

//从库数据源

@Bean

public DataSource slaveDs() {

orgapachetomcatjdbcpoolDataSource dataSource = new orgapachetomcatjdbcpoolDataSource();

dataSourcesetDriverClassName("commysqljdbcDriver");

dataSourcesetUrl("jdbc:mysql://localhost:3306/javacode2018_slavecharacterEncoding=UTF-8");

dataSourcesetUsername("root");

dataSourcesetPassword("root123");

dataSourcesetInitialSize(5);

return dataSource;

}

//读写分离路由数据源

@Bean

public ReadWriteDataSource dataSource() {

ReadWriteDataSource dataSource = new ReadWriteDataSource();

//设置主库为默认的库,当路由的时候没有在datasource那个map中找到对应的数据源的时候,会使用这个默认的数据源

dataSourcesetDefaultTargetDataSource(thismasterDs());

//设置多个目标库

Map<Object, Object> targetDataSources = new HashMap<>();

targetDataSourcesput(DsTypeMASTER, thismasterDs());

targetDataSourcesput(DsTypeSLAVE, thisslaveDs());

dataSourcesetTargetDataSources(targetDataSources);

return dataSource;

}

//JdbcTemplate,dataSource为上面定义的注入读写分离的数据源

@Bean

public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {

return new JdbcTemplate(dataSource);

}

//定义事务管理器,dataSource为上面定义的注入读写分离的数据源

@Bean

public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

}

43、UserService

这个类就相当于我们平时写的service,我是为了方法,直接在里面使用了JdbcTemplate来 *** 作数据库,真实的项目 *** 作db会放在dao里面。

getUserNameById方法:通过id查询name。

insert方法:插入数据,这个内部的所有 *** 作都会走主库,为了验证是不是查询也会走主库,插入数据之后,我们会调用thisuserServicegetUserNameById(id, DsTypeSLAVE)方法去执行查询 *** 作,第二个参数故意使用SLAVE,如果查询有结果,说明走的是主库,否则走的是从库,这里为什么需要通过thisuserService来调用getUserNameById?

thisuserService最终是个代理对象,通过代理对象访问其内部的方法,才会被读写分离的拦截器拦截。

package comjavacode2018readwritesplitdemo1;

import comjavacode2018readwritesplitbaseDsType;

import comjavacode2018readwritesplitbaseIService;

import orgspringframeworkbeansfactoryannotationAutowired;

import orgspringframeworkjdbccoreJdbcTemplate;

import orgspringframeworkstereotypeComponent;

import orgspringframeworktransactionannotationPropagation;

import orgspringframeworktransactionannotationTransactional;

import javautilList;

@Component

public class UserService implements IService {

@Autowired

private JdbcTemplate jdbcTemplate;

@Autowired

private UserService userService;

@Transactional(propagation = PropagationSUPPORTS, readOnly = true)

public String getUserNameById(long id, DsType dsType) {

String sql = "select name from t_user where id=";

List<String> list = thisjdbcTemplatequeryForList(sql, Stringclass, id);

return (list != null && listsize() > 0) listget(0) : null;

}

//这个insert方法会走主库,内部的所有 *** 作都会走主库

@Transactional

public void insert(long id, String name) {

Systemoutprintln(Stringformat("插入数据{id:%s, name:%s}", id, name));

thisjdbcTemplateupdate("insert into t_user (id,name) values (,)", id, name);

String userName = thisuserServicegetUserNameById(id, DsTypeSLAVE);

Systemoutprintln("查询结果:" + userName);

}

}

44、测试用例

package comjavacode2018readwritesplitdemo1;

import comjavacode2018readwritesplitbaseDsType;

import orgjunitBefore;

import orgjunitTest;

import orgspringframeworkcontextannotationAnnotationConfigApplicationContext;

public class Demo1Test {

UserService userService;

@Before

public void before() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

contextregister(MainConfigclass);

contextrefresh();

thisuserService = contextgetBean(UserServiceclass);

}

@Test

public void test1() {

Systemoutprintln(thisuserServicegetUserNameById(1, DsTypeMASTER));

Systemoutprintln(thisuserServicegetUserNameById(1, DsTypeSLAVE));

}

@Test

public void test2() {

long id = SystemcurrentTimeMillis();

Systemoutprintln(id);

thisuserServiceinsert(id, "张三");

}

}

test1方法执行2次查询,分别查询主库和从库,输出:

master库

slave库

是不是很爽,由开发者自己控制具体走主库还是从库。

test2执行结果如下,可以看出查询到了刚刚插入的数据,说明insert中所有 *** 作都走的是主库。

1604905117467

插入数据{id:1604905117467, name:张三}

查询结果:张三

5、案例源码

Spring 源码分析之事务

Spring 源码分析之事务2 TransactionStatus与TransactionInfo

Spring 源码分析之事务3 事务的提交与回滚

首先说一下两个类的作用:

TransactionStatus 以及子类:主要描述当前事务的状态,比如:是否有事务,是否是新事物,事务是否只读;回滚点相关 *** 作等等。这些相关的属性在后面会影响事务的提交。

TransactionInfo 主要是持有事务的状态,以及上一个TransactionInfo 的一个引用,并与当前线程进行绑定。主要是为了保证当前请求持有的是自己的事务对象,根据自己的事务状态决定事务的提交与否。

该类结构图如下:

通过分析我们发现TransactionStatus接口的实现类抽象类AbstractTransactionStatus类主要完成以下功能:

如果想要使用事务的回滚相关 *** 作,继承了 AbstractTransactionStatus 类的子类必须提供实现,否则在使用事务的回滚点相关 *** 作的时候会抛出异常。

该类还完成了事务相关功能:

我们可以全局的看看DefaultTransactionStatus结构图:

AbstractTransactionStatus 中包含了3条属性:

所以 DefaultTransactionStatus 对象最终包含的信息有:

AbstractTransactionStatus 的完成的主要功能:

1回滚点相关 *** 作(是否有回滚点、设置回滚点、获取回滚点,创建回滚点)

2标记事务已经完成

首先 protected TransactionInfo createTransactionIfNecessary 这个方法会返回一个TransactionInfo ,即在创建事务的时候,会返回。

如下:

查看prepareTransactionInfo()方法

此方法完成如下功能:

完整的类如下:

TransactionInfo 是抽象类TransactionAspectSupport的一个内部类

如果存在一个业务中法中调用多个其他业务,比如:ServiceA的a方法,调用了ServiceB的b方法和ServiceC的c方法。在调用ServiceB的和调用ServiceC的时候当前的事务对象是什么样子的,是和bindToThread()方法有关的,也就是和属性oldTransactionInfo 和持有transactionInfo的当前线程有关。

这里也可以继续关注一下 restoreThreadLocalStatus 方法。

没调用一个业务的时候,每个业务方法都有自己的TransactionInfo,每次执行的时候,都会把当前线程中的TransactionInfo取出来,然后新建自己的TransactionInfo,并在自己的TransactionInfo中,将刚刚取出来的TransactionInfo作为oldTransactionInfo 记录到自己的TransactionInfo中。然后每次执行完一个自己的业务逻辑执行,都会再次执行restoreThreadLocalStatus()方法,也就是将本地线程中的TransactionInfo设置为当前TransactionInfo中的oldTransactionInfo。保证每次执行自己的业务的时候,使用的是自己的TransactionInfo即可,保证事务对象不会发生错乱。

jdbc驱动

java jdbc连接_2019最新JDBC面试题(附JDBC教程)

weixin_39665379

关注

0点赞·410人阅读

1什么是JDBC API,何时使用它?

Java DataBase Connectivity API允许我们使用关系数据库。JDBC API接口和类是part javasql和javaxsqlpackage的一部分。我们可以使用JDBC API来获取数据库连接,在数据库服务器中运行SQL查询和存储过程并处理结果。

JDBC API的编写方式允许我们的Java程序和实际的JDBC驱动程序之间的松散耦合,这使我们可以轻松地从一个数据库切换到另一个数据库服务器。

2JDBC驱动程序有哪几种类型?

有四种类型的JDBC驱动程序。任何与数据库一起工作的java程序都有两个部分,第一部分是JDBC API,第二部分是执行实际工作的驱动程序。

JDBC-ODBC Bridge加ODBC驱动程序(类型1):它使用ODBC驱动程序连接到数据库。我们应该安装ODBC驱动程序来连接数据库,这就是为什么这个驱动程序几乎已经过时的原因。

Native API部分支持Java技术的驱动程序(类型2):此驱动程序将JDBC类转换为数据库服务器的客户端API。我们应该安装数据库客户端API。由于对数据库客户端API驱动程序的额外依赖性,这也不是首选驱动程序。

用于数据库中间件的纯Java驱动程序(类型3):此驱动程序将JDBC调用发送到可以连接到不同类型数据库的中间件服务器。我们应该安装一个中间件服务器来使用这个驱动程序。这增加了额外的网络调用和性能降低,这就是为什么不广泛使用JDBC驱动程序。

直接到数据库的纯Java驱动程序(类型4):此驱动程序将JDBC调用转换为数据库服务器可以理解的网络协议。该解决方案简单,适用于网络上的数据库连接。但是对于此解决方案,我们应该使用特定于数据库的驱动程序,例如Oracle for Oracle DB的OJDBC jar和MySQL Connector的MySQL Connector / J

3JDBC API如何帮助我们实现Java程序和JDBC驱动程序API之间的松散耦合?

JDBC API使用Java Reflection API实现java程序和JDBC驱动程序之间的松散耦合。如果你看一个简单的JDBC例子,你会发现所有的编程都是用JDBC API完成的,而驱动程序只有在通过反射使用ClassforName()方法加载时才会出现。

我认为这是在核心java类中使用Reflection的最佳示例之一,以确保我们的应用程序不能直接使用Drivers API,这使得从一个数据库移动到另一个数据库变得非常容易。

4什么是JDBC连接?解释在简单的java程序中获取数据库连接的步骤。

JDBC Connection就像使用数据库服务器创建的Session。您还可以将Connection 视为来自数据库服务器的Socket连接。

创建JDBC连接非常简单,需要两个步骤:

注册并加载驱动程序:使用ClassforName(),驱动程序类注册到DriverManager并加载到内存中。

使用DriverManager获取Connection对象:我们DriverManagergetConnection()通过传递数据库URL字符串,用户名和密码作为参数来获取连接对象。

Connection

5JDBC DriverManager类有什么用?

JDBC DriverManager是我们通过它获取数据库连接对象的工厂类。当我们加载JDBC Driver类时,它将自己注册到DriverManager,您可以查找JDBC Driver类源代码来检查它。

当我们通过传递数据库配置细节来调用方法DriverManagergetConnection()时,DriverManager使用已注册的驱动程序来获取Connection并将其返回给调用者程序。

6如何在java程序中获取数据库服务器的详细信息?

我们可以使用DatabaseMetaDataobject来获取数据库服务器的详细信息。成功创建数据库连接后,我们可以通过调用getMetaData()方法获取元数据对象。我们可以使用DatabaseMetaData中的方法来获取数据库产品名称,版本和详细的配置信息。

DatabaseMetaData

7什么是JDBC Statement?

JDBC API Statement用于在数据库中执行SQL查询。我们可以通过调用Connection createStatement()方法来创建Statement对象。我们可以使用Statement通过不同的执行方法传递查询来执行静态SQL查询,例如execute(),executeQuery(),executeUpdate()等。

由于查询是在java程序中生成的,如果未正确验证用户输入,则可能导致SQL注入问题,可以在SQL注入示例中找到更多详细信息。

默认情况下,每个Statement对象只能同时打开一个ResultSet对象。因此,如果我们想要使用多个ResultSet对象,则每个对象必须由不同的Statement对象生成。Statement接口中的所有execute()方法都隐式关闭一个statment的当前ResultSet对象(如果存在open对象)。

8execute,executeQuery,executeUpdate有什么区别?

Statement execute(String query)用于执行任何SQL查询,如果结果是ResultSet(如运行Select查询),则返回TRUE。当没有ResultSet对象(如运行Insert或Update查询)时,输出为FALSE。我们可以使用getResultSet()获取ResultSet和getUpdateCount()方法来检索更新计数。

Statement executeQuery(String query)用于执行Select查询并返回ResultSet。即使没有与查询匹配的记录,返回的ResultSet也永远不会为null。执行select查询时,我们应该使用executeQuery方法,这样如果有人试图执行insert / update语句,它将抛出javasqlSQLException,并显示消息“executeQuery方法不能用于更新”。

语句executeUpdate(String query)用于执行不返回任何内容的Insert / Update / Delete(DML)语句或DDL语句。输出为int,等于SQL数据 *** 作语言(DML)语句的行数。对于DDL语句,输出为0。

只有在不确定语句类型时才应使用execute()方法,否则使用executeQuery或executeUpdate方法。

9什么是JDBC PreparedStatement?

JDBC PreparedStatement对象表示预编译的SQL语句。我们可以使用它的setter方法来设置查询的变量。

由于PreparedStatement是预编译的,因此可以使用它多次有效地执行此语句。PreparedStatement是Statement的更好选择,因为它会自动转义特殊字符并避免SQL注入攻击。

10如何在JDBC PreparedStatement中设置NULL值?

我们可以使用PreparedStatement setNull()方法将null变量绑定到参数。例如,setNull方法将index和SQL Types作为参数 pssetNull(10, javasqlTypesINTEGER);。

11Statement中的getGeneratedKeys()方法有什么用?

有时,表可以使用自动生成的键来插入主键的唯一列值。我们可以使用Statement getGeneratedKeys()方法获取此自动生成密钥的值。

12PreparedStatement对Statement有什么好处?

PreparedStatement对Statement的一些好处是:

PreparedStatement帮助我们防止SQL注入攻击,因为它会自动转义特殊字符。

PreparedStatement允许我们使用参数输入执行动态查询。

PreparedStatement比Statement快。当我们重用PreparedStatement或使用它的批处理方法执行多个查询时,它变得更加明显。

PreparedStatement帮助我们使用setter方法编写面向对象的代码,而使用Statement我们必须使用String Concatenation来创建查询。如果要设置多个参数,则使用字符串连接编写查询看起来非常难看并且容易出错。

13PreparedStatement的限制是什么以及如何克服它?

PreparedStatement的一个限制是我们不能直接在IN子句中使用它。将PreparedStatement与IN子句一起使用的一些替代方法是:

执行单一查询 性能非常慢,不推荐使用

使用存储过程 特定于数据库,因此不适用于多个数据库应用程序。

动态创建PreparedStatement查询 良好的方法但失去了缓存的PreparedStatement的好处。

在PreparedStatement查询中使用NULL 当您知道变量输入的最大数量时,这是一种很好的方法,可以通过部分执行来扩展以允许无限制的参数。可以在JDBC PreparedStatement IN子句替代中找到更详细的分析。

14什么是JDBC ResultSet?

JDBC ResultSet就像一个表示数据库结果集的数据表,通常通过执行查询数据库的语句来生成。

ResultSet对象维护指向其当前数据行的游标。最初,光标位于第一行之前。next()方法将光标移动到下一行。如果没有更多行,则next()方法返回false,并且可以在while循环中使用它来迭代结果集。

默认的ResultSet对象不可更新,并且只有一个向前移动的游标。因此,您只能迭代一次,并且只能从第一行到最后一行。可以使用以下语法生成可滚动和/或可更新的ResultSet对象。

Statement stmt = concreateStatement(ResultSetTYPE_SCROLL_INSENSITIVE,

ResultSetCONCUR_UPDATABLE);

当生成它的Statement对象关闭,重新执行或用于从多个结果序列中检索下一个结果时,ResultSet对象将自动关闭。

我们可以使用ResultSet getter方法,列名或索引号从1开始检索列数据。

15ResultSet有哪些不同的类型?

在创建Statement时,我们可以根据用户输入获得不同类型的ResultSet对象。如果您将查看Connection方法,您将看到createStatement()和prepareStatement()方法被重载以提供ResultSet类型和并发作为输入参数。

ResultSet对象有三种类型。

ResultSetTYPEFORWARDONLY:这是默认类型,游标只能在结果集中向前移动。

ResultSetTYPESCROLLINSENSITIVE:游标可以前后移动,结果集对创建结果集后其他人对数据库所做的更改不敏感。

ResultSetTYPESCROLLSENSITIVE:游标可以向前和向后移动,结果集对创建结果集后其他人对数据库所做的更改很敏感。基于并发性,有两种类型的ResultSet对象。

ResultSetCONCURREADONLY:结果集是只读的,这是默认的并发类型。

ResultSetCONCUR_UPDATABLE:我们可以使用ResultSet更新方法来更新行数据。

16Statement中的setFetchSize()和setMaxRows()方法有什么用?

我们可以使用setMaxRows(int i)方法来限制数据库从查询返回的行数。您可以使用SQL查询本身实现相同的功能。例如,在MySQL中,我们可以使用LIMIT子句来设置查询返回的最大行数。

理解fetchSize可能很棘手,因为你应该知道Statement和ResultSet是如何工作的。当我们在数据库中执行查询时,将在数据库缓存中获取并维护结果,并返回ResultSet。ResultSet是具有对数据库中结果的引用的游标。

假设我们有一个返回100行的查询,并且我们将fetchSize设置为10,因此在每次数据库访问时,JDBC驱动程序将只获取10行,因此将有10次访问以获取所有行。如果每行需要大量处理时间并且结果中的行数很大,那么设置最佳fetchSize会很有帮助。

我们可以通过Statement对象设置fetchSize,但可以通过ResultSet对象setFetchSize()方法覆盖它。

17如何使用JDBC API调用存储过程?

存储过程是一组SQL查询,它们在数据库中编译,可以从JDBC API执行。JDBC CallableStatement可用于在数据库中执行存储过程。初始化CallableStatement的语法是;

CallableStatement

//我们需要在调用存储过程之前注册外部参数

stmt

我们需要在执行CallableStatement之前注册OUT参数。

18什么是JDBC批处理,它有什么好处?

有时我们需要为数据库运行类似的批量查询,例如将数据从CSV文件加载到关系数据库表。我们知道我们可以选择使用Statement或PreparedStatement来执行查询。除此之外,JDBC API还提供了批处理功能,通过该功能,我们可以一次性为数据库执行大量查询。

JDBC API支持通过Statement和PreparedStatement addBatch()以及executeBatch()方法进行批处理。

批处理比一次执行一个语句更快,因为数据库调用的数量较少。

19什么是JDBC事务管理?我们为什么需要它?

默认情况下,当我们创建数据库连接时,它以自动提交模式运行。这意味着无论何时执行查询并完成查询,都会自动触发提交。因此,我们触发的每个SQL查询都是一个事务,如果我们运行一些DML或DDL查询,则每个SQL语句完成后,更改都会保存到数据库中。

有时我们希望一组SQL查询成为事务的一部分,以便我们可以在所有查询运行正常时提交它们,如果我们得到任何异常,我们可以选择回滚作为事务的一部分执行的所有查询。

JDBC API提供了一种方法,setAutoCommit(boolean flag)通过该方法我们可以禁用连接的自动提交功能。我们应该仅在需要时禁用自动提交,因为除非我们在连接上调用commit()方法,否则不会提交事务。数据库服务器使用表锁来实现事务管理,这是资源密集型过程。所以我们应该在完成交易后立即提交交易。

20如何回滚JDBC事务?

我们可以使用Connection对象rollback()方法来回滚事务。它将回滚事务所做的所有更改,并释放此Connection对象当前持有的所有数据库锁。

21什么是JDBC Savepoint?如何使用它?

有时,事务可以是多个语句的组,我们希望回滚到事务中的特定点。JDBC Savepoint帮助我们在事务中创建检查点,并且我们可以回滚到该特定检查点。

为事务创建的任何保存点都会自动释放,并在提交事务时或在回滚整个事务时变为无效。将事务滚动回保存点会自动释放并使在相关保存点之后创建的任何其他保存点无效。

22什么是JDBC DataSource?它的好处是什么?

JDBC DataSource是javaxsql包中定义的接口,它比DriverManager更强大,可用于数据库连接。我们可以使用DataSource来创建数据库连接,而Driver实现类可以用来获取连接的实际工作。除了获取数据库连接外,DataSource还提供了一些其他功能,例如:

缓存PreparedStatement以加快处理速度 连接超时设置 记录功能 ResultSet最大大小阈值 使用JNDI支持在servlet容器中连接池 在JDBC DataSource上阅读有关DataSource的更多信息。

23如何在Apache Tomcat Server中使用JDBC DataSource和JNDI实现JDBC连接池?

对于部署在servlet容器中的Web应用程序,创建JDBC连接池非常简单,只需要几个步骤。

在容器配置文件中创建JDBC JNDI资源,通常是serverxml或contextxml。例如

server

在Web应用程序中,使用InitialContext查找在第一步中配置的JNDI资源,然后获取连接。

Context

24什么是Apache DBCP API?

如果您使用DataSource获取数据库连接,通常用于获取连接的代码与特定于驱动程序的DataSource实现紧密耦合。除了选择DataSource实现类之外,大多数代码都是样板代码。

Apache DBCP通过提供DataSource实现来帮助我们摆脱这些问题,DataSource实现充当我们的程序和不同JDBC驱动程序之间的抽象层。Apache DBCP库依赖于Commons Pool库,因此请确保它们都在构建路径中。

25什么是JDBC连接隔离级别?

当我们使用JDBC事务来实现数据完整性时,DBMS使用锁来阻止其他人访问事务所访问的数据。DBMS使用锁来防止脏读,不可重复读和幻像读问题。

DBMS使用JDBC事务隔离级别来使用锁定机制,我们可以通过Connection getTransactionIsolation()方法获取隔离级别信息,并使用setTransactionIsolation()方法设置它。

26什么是JDBC RowSet?RowSet有哪些类型?

JDBC RowSet以更灵活的方式保存表格数据,即ResultSet。所有RowSet对象都是从ResultSet派生的,因此它们具有ResultSet的所有功能以及一些其他功能。RowSet接口在javaxsql包中定义。

RowSet提供的一些附加功能包括:

具有属性的Java Bean及其getter-setter方法。RowSet使用JavaBeans事件模型,它们可以向任何已注册的组件发送通知,用于事件,例如光标移动,更新/插入/删除行以及更改为RowSet内容。

默认情况下,RowSet对象是可滚动和可更新的,因此如果DBMS不支持可滚动或可更新的ResultSet,我们可以使用RowSet来获取这些功能。

RowSet大致分为两种类型:

已连接的RowSet对象 - 这些对象连接到数据库,与ResultSet对象最相似。JDBC API仅提供一个连接的RowSet对象javaxsqlrowsetJdbcRowSet,它的标准实现类是comsunrowsetJdbcRowSetImpl 断开连接的RowSet对象 - 这些RowSet对象不需要连接到数据库,因此它们更轻量级且可序列化。它们适合通过网络发送数据。有四种类型的断开连接的RowSet实现。

CachedRowSet - 它们可以获取连接并执行查询并读取ResultSet数据以填充RowSet数据。我们可以在数据断开连接时 *** 作和更新数据,并重新连接到数据库并写入更改。WebRowSet派生自CachedRowSet - 它们可以读写XML文档。JoinRowSet派生自WebRowSet - 它们可以形成SQL JOIN而无需连接到数据源。从WebRowSet派生的FilteredRowSet - 我们可以应用过滤条件,以便只有选定的数据可见。

27ResultSet和RowSet有什么不同?

RowSet对象派生自ResultSet,因此它们具有ResultSet的所有功能以及一些附加功能。RowSet的一大好处是它们可以断开连接,使其轻量级,并且易于通过网络传输。

是否使用ResultSet或RowSet取决于您的要求,但如果您计划使用ResultSet更长的持续时间,则断开连接的RowSet是释放数据库资源的更好选择。

28常见的JDBC异常有哪些?

一些常见的JDBC异常是:

javasqlSQLException - 这是JDBC异常的基本异常类。javasqlBatchUpdateException - 当Batch *** 作失败时抛出此异常,但它依赖于JDBC驱动程序是否抛出此异常或基本SQLException。javasqlSQLWarning - 用于SQL *** 作中的警告消息。javasqlDataTruncation - 当数据值因超出MaxFieldSize而被意外截断时。

29JDBC中的CLOB和BLOB数据类型是什么?

字符大对象(CLOB)是由具有关联代码页的单字节字符组成的字符串。此数据类型适用于存储面向文本的信息,其中信息量可能超出常规VARCHAR数据类型的限制(上限为32K字节)。

二进制大对象(BLOB)是由字节组成的二进制字符串,没有关联的代码页。此数据类型可以存储大于VARBINARY(32K限制)的二进制数据。此数据类型适用于存储图像,语音,图形和其他类型的业务或特定于应用程序的数据。

30什么是JDBC中的“脏读”?哪个隔离级别可防止脏读?

当我们处理事务时,有可能更新行,同时其他查询可以读取更新的值。这会导致脏读,因为更新后的值不是永久性的,已更新行的事务可以回滚到先前的值,从而导致无效数据。

隔离级别TRANSACTIONREADCOMMITTED,TRANSACTION_REPEATABLEREAD和TRANSACTIONSERIALIZABLE阻止了脏读。

31什么是2阶段提交?

当我们在涉及多个数据库的分布式系统中工作时,我们需要使用2阶段提交协议。2阶段提交协议是分布式系统的原子承诺协议。在第一阶段,事务管理器向所有事务资源发送commit-request。如果所有事务资源都正常,则事务管理器将为所有资源提交事务更改。如果任何事务资源响应为Abort,则事务管理器可以回滚所有事务更改。

32JDBC中有哪些不同类型的锁定?

从广义上讲,有两种类型的锁定机制可以防止数据损坏,因为多个用户使用相同的数据。

乐观锁定 - 使用代码实现此锁定。表中引入了一个额外的列以保持更新计数。当您选择该行时,您也会阅读此列,比如“version”。现在,当您尝试更新/删除行时,将在where子句中传递此“version”。因此,如果在其间执行其他线程的更新,则更新将失败。这是避免数据损坏的好方法,但如果有人错过更新其更新语句中的“version”,则可能容易出错。通过这种锁定方式,更新查询看起来如下所示。

mysql> update emp SET name = ‘David’, version = 5 WHERE id = 10 and version = 4;

悲观锁定 - 将记录从选择锁定到读取,更新和提交阶段。这通常由数据库供应商软件完成,并通过使用SELECT FOR UPDATE查询触发。如果线程处理锁定较长时间,则这种锁定行的方式可能导致性能降低和死锁。

除此之外,一些DBMS系统提供锁定机制来锁定单行,表或数据库。

33你对DDL和DML语句有什么了解?

数据定义语言(DDL)语句用于定义数据库模式。创建,更改,删除,截断,重命名语句属于DDL语句,通常它们不返回任何结果。

数据 *** 作语言(DML)语句用于 *** 纵数据库模式中的数据。选择,插入,更新,删除,调用等是DML语句的示例。

34javautilDate和javasqlDate有什么区别?

javautilDate包含有关日期和时间的信息,而javasqlDate包含仅有关日期的信息,它没有时间信息。因此,如果您必须在数据库中保留时间信息,建议使用Timestamp或DateTime字段。

35如何将图像或原始数据插入数据库?

我们可以使用BLOB将图像或原始二进制数据插入数据库。

36什么是幻像读取以及哪种隔离级别阻止了它?

虚拟读取是指事务多次执行查询并获取不同数据的情况。假设事务正在执行查询以根据条件获取数据,然后另一个事务插入与条件匹配的行。现在,当同一事务再次执行查询时,新行将成为结果集的一部分。这个新行被称为Phantom Row,这种情况称为Phantom Read。

只有使用TRANSACTION_SERIALIZABLE隔离级别才能阻止幻像读取。

37什么是SQL警告?如何在JDBC程序中检索SQL警告?

SQLWarning是SQLException的子类,我们可以通过在Connection,Statement和ResultSet对象上调用getWarnings()方法来检索它。SQL警告不会停止脚本的执行,但会警告用户警告。

38如何使用数据库对象作为IN / OUT调用Oracle存储过程?

如果Oracle存储过程具有作为数据库对象的IN / OUT参数,那么我们需要在程序中创建相同大小的Object数组,然后使用它来创建Oracle STRUCT对象。然后我们可以通过调用setSTRUCT()方法为数据库对象设置此STRUCT对象并使用它。

39我们什么时候得到javasqlSQLException:找不到合适的驱动程序?

当SQL URL字符串格式不正确时,您将得到没有合适的驱动程序发现异常。您可以在使用DriverManager的简单Java应用程序或使用DataSource的JNDI资源中获取此异常。异常堆栈跟踪如下所示。

org

在调试此异常时,只需检查日志中打印的URL,如上面的日志中URL URL是’jdbc:mysql:// localhost:3306 / UserDB,而它应该是jdbc:mysql:// localhost:3306 / UserDB 。

40JDBC有哪些最佳实践?

一些JDBC最佳实践是:

数据库资源很重,因此请确保在完成后立即关闭它。Connection,Statement,ResultSet和所有其他JDBC对象都定义了close()方法来关闭它们。

始终在代码中显式关闭结果集,语句和连接,因为如果您在连接池环境中工作,则可能会将连接返回到池,从而导致打开的结果集和语句对象导致资源泄漏。

关闭finally块中的资源,以确保即使在异常情况下它们也会关闭。

使用批处理进行类似的批量 *** 作。

始终对Statement使用PreparedStatement以避免SQL注入并获得PreparedStatement的预编译和缓存优势。

如果要将批量数据检索到结果集中,则为fetchSize设置最佳值有助于获得良好的性能。

数据库服务器可能不支持所有隔离级别,因此请在假设之前进行检查。

更严格的隔离级别会导致性能降低,因此请确保为数据库连接设置了最佳隔离级别。

如果要在Web应用程序中创建数据库连接,请尝试使用JNDI上下文使用JDBC DataSource资源来重用连接。

当您需要长时间使用ResultSet时,请尝试使用断开连接的RowSet。

如果您需要查询芝加哥银行的转账凭证,可以按照以下步骤进行:

1 登录芝加哥银行的官方网站或者拨打芝加哥银行的客服电话,获取相关查询信息。

2 提供转账凭证的相关信息,例如转账日期、转账金额、转账账户等信息。

3 根据提供的信息,芝加哥银行的客服人员会帮助您查询转账凭证的相关信息。

4 如果您需要查询的转账凭证已经过期或者不存在,芝加哥银行的客服人员会告知您相关情况,并提供相应的解决方案。

需要注意的是,不同的银行查询转账凭证的具体流程和要求可能会有所不同,因此在查询之前,最好先了解银行的相关规定和流程。

xml文本中属性值包含<>号没法读取,- 号可能也不行,符件里的文本修改了下。

你可以引用Microsoft XML, v60库,有几个版本,用new来建立xmlDocument对象,这可以获得点提示帮助。

下面代码把各个标签的属性值输出到立即窗口:

Private Sub Command1_Click()

    Dim xmldoc

    Set xmldoc = CreateObject("msxml2domdocument")

    xmldocasync = False

    xmldocLoad "c:\xmlxml" '读取文件

    Dim transactionTag As Object '获取transaction标签

    Set transactionTag = xmldocGetelementsbytagname("transaction")(0)

    DebugPrint transactionTagAttributesgetNamedItem("type")Text, transactionTagAttributesgetNamedItem("vaultname")Text

    Dim documentTag As Object '获取document标签

    Set documentTag = transactionTagchildnodes(0)

    DebugPrint , documentTagAttributesgetNamedItem("aliasset")Text, documentTagAttributesgetNamedItem("pdmweid")Text

    Dim configurationTag As Object

    Dim attributeTag As Object

    Dim Attributes   As Object

    For Each configurationTag In documentTagchildnodes '遍历configuration标签

        DebugPrint , , configurationTagAttributesgetNamedItem("name")Text, configurationTagAttributesgetNamedItem("quantity")Text

        For Each attributeTag In configurationTagchildnodes '遍历attribute标签

            Set Attributes = attributeTagAttributes

            DebugPrint , , , AttributesgetNamedItem("name")Text, AttributesgetNamedItem("value")Text

        Next

    Next

End Sub

   上一篇 我们介绍了 spring-tx 中的底层抽象,本篇我们一起来看看围绕这些抽象概念 spring-tx 是如何打造出声明式事务的吧。笼统的说, spring-tx-526RELEASE 的实现主要分为两个部分:

这两部分彼此独立又相互成就,并且每个部分都有着大量的源码支撑,本篇我们先来分析 spring-tx 中的AOP部分吧。

   EnableTransactionManagement 注解想必大家都很熟悉了,它是启用 Spring 中注释驱动的事务管理功能的关键。

EnableTransactionManagement 注解的主要作用是向容器中导入 TransactionManagementConfigurationSelector ,至于注解中定义的几个属性在 Spring AOP源码解析 中有过详细分析,这里就不再赘述了。

由于我们并没有使用 AspectJ ,因此导入容器的自然是 ProxyTransactionManagementConfiguration 这个配置类。

  这个配置类的核心是向容器中导入一个类型为 BeanFactoryTransactionAttributeSourceAdvisor 的Bean。这是一个 PointcutAdvisor ,它的 Pointcut 是 TransactionAttributeSourcePointcut , Advice 是 TransactionInterceptor 。

   TransactionAttributeSourcePointcut 利用 TransactionAttributeSource 解析 @Transactional 注解的能力来选取标注了 @Transactional 注解的方法,而 TransactionInterceptor 则根据应用提出的需求(来自对 @Transactional 注解的解析)将方法增强为事务方法,因此 BeanFactoryTransactionAttributeSourceAdvisor 可以识别出那些标注了 @Transactional 注解的方法,为它们应用上事务相关功能。

   TransactionInterceptor 能对方法进行增强,但是它却不知道该如何增强,比如是为方法新开一个独立事务还是沿用已有的事务?什么情况下需要回滚,什么情况下不需要?必须有一个『人』告诉它该如何增强,这个『人』便是 TransactionAttributeSource 。

   @Transactional 注解定义了事务的基础信息,它表达了应用程序期望的事务形态。 TransactionAttributeSource 的主要作用就是解析 @Transactional 注解,提取其属性,包装成 TransactionAttribute ,这样 TransactionInterceptor 的增强便有了依据。

前面我们已经见过, spring-tx 使用 AnnotationTransactionAttributeSource 来做具体的解析工作,其父类 AbstractFallbackTransactionAttributeSource 定义了解析 TransactionAttribute 的优先级,核心方法是 computeTransactionAttribute() 。

AnnotationTransactionAttributeSource 默认只解析 public 修饰的方法,这也是导致 @Transactional 注解失效的一个原因,除此之外它还实现了父类中定义的两个模板方法:

同时为了支持 EJB 中定义的 javaxejbTransactionAttribute 和 JTA 中定义的 javaxtransactionTransactional 注解, AnnotationTransactionAttributeSource 选择将实际的提取工作代理给 TransactionAnnotationParser 。Spring 提供的 @Transactional 注解由 SpringTransactionAnnotationParser 进行解析。

SpringTransactionAnnotationParser 的源码还是很简单的,它使用 AnnotatedElementUtils 工具类定义的 find 语义来获取 @Transactional 注解信息。 RuleBasedTransactionAttribute 中 rollbackOn() 的实现还是挺有意思的,其它的都平平无奇。

RollbackRuleAttribute 是用来确定在发生特定类型的异常(或其子类)时是否应该回滚,而 NoRollbackRuleAttribute 继承自 RollbackRuleAttribute ,但表达的是相反的含义。 RollbackRuleAttribute 持有某个异常的名称,通过 getDepth(Throwable ex) 算法来计算指定的 Throwable 和持有的异常在继承链上的距离。

  程序猿只有在拿到需求以后才能开工, TransactionInterceptor 也一样,有了 TransactionAttributeSource 之后就可以有依据的增强了。观察类图, TransactionInterceptor 实现了 MethodInterceptor 接口,那么自然要实现接口中的方法:

可以看到, TransactionInterceptor 本身是没有实现任何逻辑的,它更像一个适配器。这样分层以后, TransactionAspectSupport 理论上就可以支持任意类型的 Advice 而不只是 MethodInterceptor 。实现上 TransactionAspectSupport 确实也考虑了这一点,我们马上就会看到。

invokeWithinTransaction() 的流程还是非常清晰的:

第一步前文已经分析过了,我们来看第二步。

TransactionInfo 是一个非常简单的类,我们就不费什么笔墨去分析它了。接着看第三步,这一步涉及到两个不同的 *** 作——提交或回滚。

至此, TransactionInterceptor 于我们而言已经没有任何秘密了。

  本篇我们一起分析了 spring-tx 是如何通过 spring-aop 的拦截器将普通方法增强为事务方法的,下篇就该说道说道 PlatformTransactionManager 抽象下的事务管理细节啦,我们下篇再见~~

可以。以下为代码:

import comatomikosicatchjtaUserTransactionManager;

import comatomikosjdbcAtomikosDataSourceBean;

import javaxjtaTransactionManager;

import javaxsqlDataSource;

public class AtomikosExample {

// Atomikos implementations

private static UserTransactionManager utm;

private static AtomikosDataSourceBean adsb;

// Standard interfaces

private static TransactionManager tm;

private static DataSource ds;

// initialize resources

private static void init() {

UserTransactionManager utm = new UserTransactionManager();

utminit();

tm = utm;

adsb = ; // omitted for clarity

ds = adsb;

}

// release resources

private static void shutdown() {

adsbclose();

utmclose();

}

public static void main(String[] args) {

init();

tmbegin();

Connection c = dsgetConnection();

// use connection to execute SQL

cclose();

tmcommit();

shutdown();

}

}

以上就是关于spring事务中查询主库还是从库全部的内容,包括:spring事务中查询主库还是从库、Spring 源码分析之事务2 TransactionStatus与TransactionInfo、在jdbcapi,statement哪个方法可以用来限制查询返回的记录数等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9546801.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存