JDBC是JDK提供的JAVA访问关系型数据库的SPI,有多种实现方式。
ODBC是微软提出的!O是open不是oracle。也是最造被开发者和数据库厂商接收的交互方案。所以可以在ODBC的基础上构建JDBC,称为JDBC-ODBC Bridge Driver。ODBC通过调用数据库的本地客户端的API和远程数据库通信交互。
当然,可以直接调用数据库本地的客户端。也就是Native API Driver。不过,JDBC实现者就无法做到硬件无关了。所以,不被推荐。
JDBC-Net Driver,Java将sql翻译为数据库无关的操作协议,发给中间服务器,中间服务器负责翻译为具体服务器的操作转发到数据库服务器。ADO.NET是使用这种方案的。
Native Protocol Driver 最常用的一种,JDBC实现者直接将命令转化为操作数据的网络请求,直接发送给数据库。mysql-connector-java就属于这一种。
JDBC对数据库操作基本如下
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
System.out.println("Connecting to database...");
//建立连接
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//执行SQL
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);
//分析结果
while(rs.next()){
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
}
JNDI和JDBC都是JDK提供的SPI接口。但同时,JDNI又是J2EE规范(意味这tomcat等这一套j2ee规范的实现组建都支持JNDI)。JNDI理论上的访问不限于数据库。JNDI访问数据库也需要JDBC驱动,所以说JNDI是JDBC的上一个层次也没错。
JNDI可以配置在诸如Tomcat这样的容器中。提供程序员数据访问,而开发者不需要在代码里关系数据访问的细节。以tomcat为例,server.xml配置文件如下,配置两个数据源
然后还需要在context.xml中进行配置
WEB-INF/web.xml
${catalina.base}/conf/web.xml
最后就是实际使用了
Connection conn=null;try {
Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS");
......
c.close();
} catch(Exception e) {
e.printStackTrace();
} finally {
if(conn!=null) {
try {
conn.close();
} catch(SQLException e) { }
}
}
常用的的比如说Hibernate和mybaitis。ORM框架还一般会整合连接池。常见的连接池有dbcp(tomcat自带),c3p0,druid等。
事务乍一看和Spring没有什么关系。但是,至少在面试中,事务被称为Spring的核心之一也没有错。这关系到你能不能稳定的写出业务代码。
具体使用请参考
https://blog.csdn.net/define_us/article/details/52261978
这里只简单论述原理。
Spring事务其实就是Spring AOP,底层创建动态代理对象,在代码的开头结尾封装了开启事务和事务回滚操作。
public interface PlatformTransactionManager {
// 获取事务状态信息
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
其实,有transaction manager就可以自己实现事务了
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
userMapper.insertUser(user);
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
一个事务的定义如下
public interface TransactionDefinition {
// 事务的传播行为
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
// 事务的隔离级别
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
// 事务的超时时间
int TIMEOUT_DEFAULT = -1;
// 获取事务的传播行为
int getPropagationBehavior();
// 获取事务的隔离级别
int getIsolationLevel();
// 获取事务的超时时间
int getTimeout();
// 获取事务是否只读
boolean isReadOnly();
// 获取事务对象名称
@Nullable
String getName();
}
事务状态如下
public interface TransactionStatus extends SavepointManager, Flushable {
// 是否是新事务
boolean isNewTransaction();
// 是否存在保存点
boolean hasSavepoint();
// 设置事务回滚
void setRollbackOnly();
// 是否回滚
boolean isRollbackOnly();
// 刷新事务
@Override
void flush();
// 获取事务是否完成
boolean isCompleted();
}
Spring的分库很简单,继承AbstractRoutingDataSource重写determineCurrentLookupKey()方法。就可以实现跨库了。但是实现跨库事务就没有这么简单了。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
}
上面说了这么多,很遗憾都是单库事务。跨库(数据源)事务,包含一些消息队列的事务可以使用JTA来实现。JTA由以下三部分组成
前3个接口位于Java EE版的类库 javaee.jar 中,JDK中没有提供。一个典型的JTA代码如下
public void transferAccount() {
UserTransaction userTx = null;
Connection connA = null;
Statement stmtA = null;
Connection connB = null;
Statement stmtB = null;
try{
// 获得 Transaction 管理对象
userTx = (UserTransaction)getContext().lookup("\
java:comp/UserTransaction");
// 从数据库 A 中取得数据库连接
connA = getDataSourceA().getConnection();
// 从数据库 B 中取得数据库连接
connB = getDataSourceB().getConnection();
// 启动事务
userTx.begin();
// 将 A 账户中的金额减少 500
stmtA = connA.createStatement();
stmtA.execute("
update t_account set amount = amount - 500 where account_id = 'A'");
// 将 B 账户中的金额增加 500
stmtB = connB.createStatement();
stmtB.execute("\
update t_account set amount = amount + 500 where account_id = 'B'");
// 提交事务
userTx.commit();
// 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
} catch(SQLException sqle){
try{
// 发生异常,回滚在本事务中的操纵
userTx.rollback();
// 事务回滚:转账的两步操作完全撤销
//( 数据库 A 和数据库 B 中的数据更新被同时撤销)
stmt.close();
conn.close();
...
}catch(Exception ignore){
}
sqle.printStackTrace();
} catch(Exception ne){
e.printStackTrace();
}
}