JDBC
——执行SQL语句的java api
操作数据库都是在JDBC API【接口】上,使用不同的数据库,只要用数据库厂商提供的数据库驱动程序即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| Connection connection = null; Statement statement = null; ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/zhongfucheng", "root", "root");
statement = connection.createStatement();
resultSet = statement.executeQuery("SELECT * FROM users");
while (resultSet.next()) {
System.out.println(resultSet.getString(1));
System.out.println(resultSet.getString(2)); } } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally {
if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); }
} if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); }
} if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
|
DriverManager类
Jdbc程序中的DriverManager用于加载驱动,并创建与数据库的链接,这个API的常用方法:
- DriverManager.registerDriver(new Driver())
- DriverManager.getConnection(url, user, password),
注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
2、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
推荐方式:Class.forName(“com.mysql.jdbc.Driver”);
采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
connection类
客户端与数据库所有的交互都是通过Connection来完成的。
常用的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
createcreateStatement()
prepareStatement(sql)
prepareCall(sql)
setAutoCommit(boolean autoCommit)
commit()
rollback()
|
Statement类
Statement对象用于向数据库发送Sql语句,对数据库的增删改查都可以通过此对象发送sql语句完成。
Statement对象的常用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
executeQuery(String sql)
executeUpdate(String sql)
execute(String sql)
addBatch(String sql)
executeBatch()
|
ResultSet类
ResultSet对象代表Sql语句的执行结果,当Statement对象执行executeQuery()时,会返回一个ResultSet对象
ResultSet对象维护了一个数据行的游标【简单理解成指针】,调用ResultSet.next()方法,可以让游标指向具体的数据行,进行获取该行的数据
常用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
getObject(String columnName)
getString(String columnName)
next()
Previous()
absolute(int row)
beforeFirst()
afterLast()
|
PreparedStatement类
PreparedStatement对象继承Statement对象,它比Statement对象更强大,使用起来更简单
- Statement对象编译SQL语句时,如果SQL语句有变量,就需要使用分隔符来隔开,如果变量非常多,就会使SQL变得非常复杂。PreparedStatement可以使用占位符,简化sql的编写
- Statement会频繁编译SQL。PreparedStatement可对SQL进行预编译,提高效率,预编译的SQL存储在PreparedStatement对象中
- PreparedStatement防止SQL注入。【Statement通过分隔符’++’,编写永等式,可以不需要密码就进入数据库】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| String id = "2";
Connection connection = UtilsDemo.getConnection();
String sql = "SELECT * FROM users WHERE id = ?"; PreparedStatement preparedStatement = connection.preparedStatement(sql);
preparedStatement.setString(1,id);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) { System.out.println(resultSet.getString("name")); }
UtilsDemo.release(connection, preparedStatement, resultSet);
|
批处理
当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条发送执行,采用批处理以提升执行效率
批处理有两种方式:
- Statement
- PreparedStatement
通过executeBath()方法批量处理执行SQL语句,返回一个int[]数组,该数组代表各句SQL的返回值
以下代码是以Statement方式实现批处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
Connection connection = UtilsDemo.getConnection();
Statement statement = connection.createStatement(); String sql1 = "UPDATE users SET name='zhongfucheng' WHERE id='3'"; String sql2 = "INSERT INTO users (id, name, password, email, birthday)" + " VALUES('5','nihao','123','ss@qq.com','1995-12-1')";
statement.addBatch(sql1); statement.addBatch(sql2);
statement.executeBatch();
statement.clearBatch();
UtilsDemo.release(connection, statement, null);
|
以下方式以PreparedStatement方式实现批处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
Connection connection = UtilsDemo.getConnection();
String sql = "INSERT INTO test(id,name) VALUES (?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 1; i <= 205; i++) { preparedStatement.setInt(1, i); preparedStatement.setString(2, (i + "zhongfucheng"));
preparedStatement.addBatch();
if (i %2 ==100) {
preparedStatement.executeBatch();
preparedStatement.clearBatch(); }
}
preparedStatement.executeBatch();
preparedStatement.clearBatch();
UtilsDemo.release(connection, preparedStatement, null);
|
Oracle
下面用JDBC连接Oracle数据库去操作大文本数据和二进制数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
public class Demo7 { @Test public void add() {
Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null;
try { connection = UtilsDemo.getConnection();
connection.setAutoCommit(false);
String sql = "insert into test4(id,image) values(?,empty_blob())"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setInt(1, 1); preparedStatement.executeUpdate();
String sql2 = "select image from test4 where id= ? for update"; preparedStatement = connection.prepareStatement(sql2); preparedStatement.setInt(1, 1); resultSet = preparedStatement.executeQuery();
if (resultSet.next()) { BLOB blob = (BLOB) resultSet.getBlob("image");
OutputStream outputStream = blob.getBinaryOutputStream();
InputStream inputStream = Demo7.class.getClassLoader().getResourceAsStream("01.jpg");
int len=0; byte[] bytes = new byte[1024]; while ((len = inputStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, len); } outputStream.close(); inputStream.close(); connection.setAutoCommit(true); } } catch (SQLException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { UtilsDemo.release(connection, preparedStatement, null); }
}
@Test public void find() { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null;
try { connection = UtilsDemo.getConnection(); String sql = "SELECT * FROM test4 WHERE id=1";
preparedStatement = connection.prepareStatement(sql); resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
BLOB blob = (BLOB) resultSet.getBlob("image");
InputStream inputStream = blob.getBinaryStream(); FileOutputStream fileOutputStream = new FileOutputStream("d:\\zhongfucheng.jpg"); int len=0; byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) > 0) {
fileOutputStream.write(bytes, 0, len); }
inputStream.close(); fileOutputStream.close();
}
} catch (SQLException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { UtilsDemo.release(connection, preparedStatement, null); } } }
|
对于JDBC连接Oracle数据库操作CLOB数据,我就不再重复了,操作跟BLOB几乎相同
获取数据库的自动主键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Test public void test() {
Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null;
try { connection = JdbcUtils.getConnection();
String sql = "INSERT INTO test(name) VALUES(?)"; preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "ouzicheng");
if (preparedStatement.executeUpdate() > 0) {
resultSet = preparedStatement.getGeneratedKeys();
if (resultSet.next()) { int id = resultSet.getInt(1); System.out.println(id); } } } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils.release(connection, preparedStatement, null); }
|
调用数据库的存储过程
调用存储过程的语法:
1
| {call <procedure-name>[(<arg1>,<arg2>, ...)]}
|
调用函数的语法:
1
| {?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
|
如果是Output类型的,那么在JDBC调用的时候是要注册的。如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
public class Demo9 {
public static void main(String[] args) { Connection connection = null; CallableStatement callableStatement = null;
try { connection = JdbcUtils.getConnection(); callableStatement = connection.prepareCall("{call demoSp(?,?)}");
callableStatement.setString(1, "nihaoa"); callableStatement.registerOutParameter(2, Types.VARCHAR); callableStatement.execute(); String result = callableStatement.getString(2); System.out.println(result);
} catch (Exception e) { e.printStackTrace(); }finally { try { connection.close(); callableStatement.close(); } catch (SQLException e) { e.printStackTrace(); } }
}
|
参考资料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| ----------------------------------------------------------------------------------过程
#修改mysql语句的结果符为 mysql > delimiter
#定义一个过程,获取users表总记录数,将10设置到变量count中 create procedure simpleproc(out count int) begin select count(id) into count from users; end
#修改mysql语句的结果符为; mysql > delimiter ;
#调用过程,将结果覆给变量a,@是定义变量的符号 call simpleproc(@a);
#显示变量a的值 select @a;
String sql = "{call simpleproc(?)}"; Connection conn = JdbcUtil.getConnection(); CallableStatement cstmt = conn.prepareCall(sql); cstmt.registerOutParameter(1,Types.INTEGER); cstmt.execute(); Integer count = cstmt.getInt(1); System.out.println("共有" + count + "人");
----------------------------------------------------------------------------------函数
#修改mysql语句的结果符为 mysql > delimiter
#定义一个函数,完成字符串拼接 create function hello( s char(20) ) returns char(50) return concat('hello,',s,'!');
#修改mysql语句的结果符为; mysql > delimiter ;
#调用函数 select hello('world');
String sql = "{? = call hello(?)}"; Connection conn = JdbcUtil.getConnection(); CallableStatement cstmt = conn.prepareCall(sql); cstmt.registerOutParameter(1,Types.VARCHAR); cstmt.setString(2,"zhaojun"); cstmt.execute(); String value = cstmt.getString(1); System.out.println(value); JdbcUtil.close(cstmt); JdbcUtil.close(conn);
|
数据库连接池
数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
编写连接池
- 编写连接池需实现java.sql.DataSource接口
- 创建批量的Connection用LinkedList保存【既然是个池,当然用集合保存、、LinkedList底层是链表,对增删性能较好】
- 实现getConnetion(),让getConnection()每次调用,都是在LinkedList中取一个Connection返回给用户
- 调用Connection.close()方法,Connction返回给LinkedList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| private static LinkedList<Connection> list = new LinkedList<>();
static { InputStream inputStream = Demo1.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties(); try { properties.load(inputStream); String url = properties.getProperty("url"); String username = properties.getProperty("username"); String driver = properties.getProperty("driver"); String password = properties.getProperty("password");
Class.forName(driver);
for (int i = 0; i < 10; i++) { Connection connection = DriverManager.getConnection(url, username, password); list.add(connection); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }
}
@Override public Connection getConnection() throws SQLException { System.out.println(list.size()); System.out.println(list);
return list.size() > 0 ? list.removeFirst() : null; }
|
我们已经完成前三步了,现在问题来了。我们调用Conncetion.close()方法,是把数据库的物理连接关掉,而不是返回给LinkedList的
解决思路:
- 写一个Connection子类,覆盖close()方法
- 写一个Connection包装类,增强close()方法
- 用动态代理,返回一个代理对象出去,拦截close()方法的调用,对close()增强
分析第一个思路:
- Connection是通过数据库驱动加载的,保存了数据的信息。写一个子类Connection,new出对象,子类的Connction无法直接继承父类的数据信息,也就是说子类的Connection是无法连接数据库的,更别谈覆盖close()方法了。
分析第二个思路:
- 写一个Connection包装类。
- 写一个类,实现与被增强对象的相同接口【Connection接口】
- 定义一个变量,指向被增强的对象
- 定义构造方法,接收被增强对象
- 覆盖想增强的方法
- 对于不想增强的方法,直接调用被增强对象的方法
- 这个思路本身是没什么毛病的,就是实现接口时,方法太多了!,所以我们也不使用此方法
分析第三个思路代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Override public Connection getConnection() throws SQLException {
if (list.size() > 0) { final Connection connection = list.removeFirst();
System.out.println(list.size());
return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!method.getName().equals("close")) { return method.invoke(connection, args); } else {
list.add(connection);
System.out.println(list.size());
} return null; }
}); } return null; }
|
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| package me.gacl.demo;
import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.LinkedList; import java.util.Properties; import javax.sql.DataSource; public class JdbcPool implements DataSource{
private static LinkedList<Connection> listConnections = new LinkedList<Connection>();
static{ InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties"); Properties prop = new Properties(); try { prop.load(in); String driver = prop.getProperty("driver"); String url = prop.getProperty("url"); String username = prop.getProperty("username"); String password = prop.getProperty("password"); int jdbcPoolInitSize =Integer.parseInt(prop.getProperty("jdbcPoolInitSize")); Class.forName(driver); for (int i = 0; i < jdbcPoolInitSize; i++) { Connection conn = DriverManager.getConnection(url, username, password); System.out.println("获取到了链接" + conn); listConnections.add(conn); }
} catch (Exception e) { throw new ExceptionInInitializerError(e); } }
@Override public PrintWriter getLogWriter() throws SQLException { return null; }
@Override public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override public void setLoginTimeout(int seconds) throws SQLException {
}
@Override public int getLoginTimeout() throws SQLException { return 0; }
@Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; }
@Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; }
@Override public Connection getConnection() throws SQLException { if (listConnections.size()>0) { final Connection conn = listConnections.removeFirst(); System.out.println("listConnections数据库连接池大小是" + listConnections.size()); return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(!method.getName().equals("close")){ return method.invoke(conn, args); }else{ listConnections.add(conn); System.out.println(conn + "被还给listConnections数据库连接池了!!"); System.out.println("listConnections数据库连接池大小为" + listConnections.size()); return null; } } }); }else { throw new RuntimeException("对不起,数据库忙"); } }
@Override public Connection getConnection(String username, String password) throws SQLException { return null; } }
|
开源数据库连接池
开源组织提供了数据源的独立实现:
DBCP
Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
使用DBCP数据源的步骤:
导入两个jar包【Commons-dbcp.jar和Commons-pool.jar】
读取配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbcstudy username=root password=XDP
#<!-- 初始化连接 --> initialSize=10
#最大连接数量 maxActive=50
#<!-- 最大空闲连接 --> maxIdle=20
#<!-- 最小空闲连接 --> minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=UTF8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=READ_UNCOMMITTED
|
获取BasicDataSourceFactory对象
创建DataSource对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| public class JdbcUtils_DBCP {
private static DataSource dataSource = null; static { try { InputStream inputStream = Demo3.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"); Properties properties = new Properties(); properties.load(inputStream);
BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory(); dataSource = basicDataSourceFactory.createDataSource(properties);
} catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } }
public static Connection getConnection() throws SQLException { return dataSource.getConnection();
}
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) { try { rs.close(); } catch (Exception e) { e.printStackTrace(); } rs = null; } if (st != null) { try { st.close(); } catch (Exception e) { e.printStackTrace(); }
} if (conn != null) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); }
} }
|
C3P0
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0数据源在项目开发中使用得比较多。
c3p0与dbcp区别
- dbcp没有自动回收空闲连接的功能
- c3p0有自动回收空闲连接功能
步骤:
- 导入开发包【c3p0-0.9.2-pre1.jar】和【mchange-commons-0.2.jar】
- 导入XML配置文件【可以在程序中自己一个一个配,C3P0的doc中的Configuration有XML文件的事例】
- new出ComboPooledDataSource对象
c3p0-config.xml的配置信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property> <property name="user">root</property> <property name="password">XDP</property>
<property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config>
<named-config name="MySQL"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property> <property name="user">root</property> <property name="password">XDP</property>
<property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </named-config>
</c3p0-config>
|
在获取数据库连接的工具类(如jdbcUtils)的静态代码块中创建池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class JdbcUtils_C3P0 {
private static ComboPooledDataSource ds = null; static{ try{
ds = new ComboPooledDataSource("MySQL");
}catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException{ return ds.getConnection(); }
public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ st.close(); }catch (Exception e) { e.printStackTrace(); } }
if(conn!=null){ try{ conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
|
配置Tomcat数据源
Tomcat服务器也给我们提供了连接池,内部其实就是DBCP
步骤:
- 在META-INF目录下配置context.xml文件【文件内容可以在tomcat默认页面的 JNDI Resources下Configure Tomcat’s Resource Factory找到】
- 导入Mysql或oracle开发包导入到tomcat的lib目录下
- 初始化JNDI->获取JNDI容器->检索以XXX为名字在JNDI容器存放的连接池
context.xml文件的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <Context>
<Resource name="jdbc/EmployeeDB" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/zhongfucheng" maxActive="8" maxIdle="4"/> </Context> try {
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB");
Connection conn = ds.getConnection(); System.out.println(conn);
}
|
在获取数据库连接的工具类(如jdbcUtils)的静态代码块中获取JNDI容器中的数据源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class JdbcUtils_JNDI {
private static DataSource ds = null; static{ try{ Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); ds = (DataSource)envCtx.lookup("jdbc/datasource"); }catch (Exception e) { throw new ExceptionInInitializerError(e); } }
public static Connection getConnection() throws SQLException{ return ds.getConnection(); } public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ st.close(); }catch (Exception e) { e.printStackTrace(); } }
if(conn!=null){ try{ conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }
|
使用dbutils框架
dbutils它是对JDBC的简单封装,极大简化jdbc编码的工作量
commons-dbutilsAPI介绍:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
工具类:
- org.apache.commons.dbutils.DbUtils
DbUtils类
提供了关闭连接,装载JDBC驱动,回滚提交事务等方法的工具类【比较少使用,因为我们学了连接池,就应该使用连接池连接数据库】
QueryRunner类
该类简化了SQL查询,配合ResultSetHandler使用,可以完成大部分的数据库操作,重载了许多的查询,更新,批处理方法。大大减少了代码量
ResultSetHandler接口
该接口规范了对ResultSet的操作,要对结果集进行什么操作,传入ResultSetHandler接口的实现类即可。
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScalarHandler 将ResultSet的一个列到一个对象中。
使用DbUtils框架对数据库的CRUD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public class Test {
@org.junit.Test public void add() throws SQLException {
QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "INSERT INTO student (id,name) VALUES(?,?)";
queryRunner.update(sql, new Object[]{"100", "zhongfucheng"});
}
@org.junit.Test public void query()throws SQLException {
|
事务
一个SESSION所进行的所有更新操作要么一起成功,要么一起失败
注意:当Connection遇到一个未处理的SQLException时,系统会非正常退出,事务也会自动回滚,但如果程序捕获到了异常,是需要在catch中显式回滚事务的。
savapoint
可以设置中间点。
注意:savepoint不会结束当前事务,普通提交和回滚都会结束当前事务的
注事务的隔离级别
数据库定义了4个隔离级别:
- Serializable【可避免脏读,不可重复读,虚读】
- Repeatable read【可避免脏读,不可重复读】
- Read committed【可避免脏读】
- Read uncommitted【级别最低,什么都避免不了】
分别对应Connection类中的4个常量
- TRANSACTION_READ_UNCOMMITTED
- TRANSACTION_READ_COMMITTED
- TRANSACTION_REPEATABLE_READ
- TRANSACTION_SERIALIZABLE
脏读:一个事务读取到另外一个事务未提交的数据
不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改
虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
注:和不可重复读类似,但虚读(幻读)会读到其他事务的插入的数据,导致前后读取不一致
简单总结:脏读是不可容忍的,不可重复读和虚读在一定的情况下是可以的【做统计的肯定就不行】。
事务处理
分页
Oracle实现分页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
SELECT *FROM ( SELECT 列名,列名,ROWNUM rn FROM 表名 WHERE ROWNUM<=(currentPage*lineSize)) temp
WHERE temp.rn>(currentPage-1)*lineSize;
|
Mysql实现分页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
SELECT * FROM 表名 LIMIT [START], length;
|
总结:
- Mysql从(currentPage-1)*lineSize开始取数据,取lineSize条数据
- Oracle先获取currentPage*lineSize条数据,从(currentPage-1)*lineSize开始取数据
面试题
参考文章