JDBC
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");
}
ORM框架
常用的的比如说Hibernate和mybaitis。ORM框架还一般会整合连接池。常见的连接池有dbcp(tomcat自带),c3p0,druid等。
程序员写SQL毕竟不开心。所以还是希望用java方法的方式来操作数据。
JPA
JPA是一个Java EE标准。当时Sun公司希望通过JPA整合ORM领域。不过后来没遵守JPA的mybatis反而流行了起来是大家没想到的。也算是JPA VS mybatis,也算是JAVA届的一个引战之道了。
JPA需要provider来实现其功能,就比如说Hibernate。
JNDI
JNDI和JDBC都是JDK提供的SPI接口。但同时,JDNI又是J2EE规范(意味这tomcat等这一套j2ee规范的实现组建都支持JNDI)。JNDI理论上的访问不限于数据库。JNDI访问数据库也需要JDBC驱动,所以说JNDI是JDBC的上一个层次也没错。JNDI是给类似运行在tomcat这样的J2EE的容器中的用户代码使用的。 当然用户可可以不用这个JNDI,自己管理数据库连接。
JNDI可以配置在诸如Tomcat这样的容器中。提供程序员数据访问,而开发者不需要在代码里关系数据访问的细节。以tomcat为例,server.xml配置文件如下,配置两个数据源
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
<Resource auth="Container"
description="DB Connection"
driverClass="oracle.jdbc.driver.OracleDriver"
maxPoolSize="20"
minPoolSize="5"
acquireIncrement="5"
maxIdleTime="300"
idleConnectionTestPeriod="60"
automaticTestTable="Test"
acquireRetryAttempts="30"
breakAfterAcquireFailure="true"
name="jdbc/amodMonDbSource"
user="amod"
password="amoddb"
factory="org.apache.naming.factory.BeanFactory"
type="com.mchange.v2.c3p0.ComboPooledDataSource"
jdbcUrl="jdbc:oracle:thin:@10.111.0.108:1521:orcl" />
<Resource auth="Container"
description="DB Connection"
driverClass="oracle.jdbc.driver.OracleDriver"
maxPoolSize="20"
minPoolSize="5"
acquireIncrement="5"
maxIdleTime="300"
idleConnectionTestPeriod="60"
automaticTestTable="Test"
acquireRetryAttempts="30"
breakAfterAcquireFailure="true"
name="jdbc/ddamodMonDbSource"
user="ddamod"
password="ddamoddb"
factory="org.apache.naming.factory.BeanFactory"
type="com.mchange.v2.c3p0.ComboPooledDataSource"
jdbcUrl="jdbc:oracle:thin:@10.111.0.108:1521:orcl" />
</GlobalNamingResources>
然后还需要在context.xml中进行配置
<!-- The contents of this file will be loaded for each web application -->
<Context>
<!-- Default set of monitored resources. If one of these changes, the -->
<!-- web application will be reloaded. -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<ResourceLink name="jdbc/amodMonDbSource" global="jdbc/amodMonDbSource" type="javax.sql.DataSource"/>
<ResourceLink name="jdbc/ddamodMonDbSource" global="jdbc/ddamodMonDbSource" type="javax.sql.DataSource"/>
</Context>
最后就是实际使用了
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) { }
}
}
JTA
同样是一个JavaEE的规范。
Spring & SpringJDBC
事务乍一看和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由以下三部分组成
- 高层应用事务界定接口,供事务客户界定事务边界的
- X/Open XA协议(资源之间的一种标准化的接口)的标准Java映射,它可以使事务性的资源管理器参与由外部事务管理器控制的事务中
- 高层事务管理器接口,允许应用程序服务器为其管理的应用程序界定事务的边界
主要包含以下接口
JTA的主要接口
位于javax.transaction包中
a、UserTransaction接口:让应用程序得以控制事务的开始、挂起、提交、回滚等。由Java客户端程序或EJB调用。
b、TransactionManager 接口:用于应用服务器管理事务状态
c、Transaction接口:用于执行相关事务操作
d、XAResource接口:用于在分布式事务环境下,协调事务管理器和资源管理器的工作
e、Xid接口:为事务标识符的Java映射
前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();
}
}
连接池
https://blog.csdn.net/define_us/article/details/80625721
分库分表中间件
国内用的比较多的是当当的sharding-jdbc.
大众点评的zebra也是一个。
myBatis
如果说JPA是面向对象的数据库操作,那么mybatis就是面向关系的。虽然不方便,自动化差一点,但是sql便携则更灵活。
SpringData
简化了数据库访问,SpringData也可以使用JPA的底层(也就是Spring Data JPA)。