1、JDBC所处位置:应用程序->JDBC API->数据库驱动->数据库
2、连接数据库的步骤:
1>注册驱动(只做一次),有如下三种方式:
//直接注册驱动,缺点:装载两次,且需要引入java.sql.DriverManager类,离开此驱动无法编译;
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//drivers是一个vector集合,直接从中获取属性,不会对具体的驱动类产生依赖,但注册不太方便
System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver:driver1:driver2");
//mysql的Driver类的静态代码块中已经注册,java.sql.DriverManager.registerDriver(new Driver());直接调用即可;优点:不会对具体的驱动类产生依赖,便于配置,无需修改代码,推荐方式;
Class.forName("com.mysql.jdbc.Driver");
2>建立连接(Connection)
Connection conn = DriverManager.getConnection(url,user,password);
url格式:
jdbc:子协议:子名称//主机名:端口/数据库名?属性名=属性值&...
如:jdbc:mysql://localhost:3306/jdbc,默认主机名:端口为localhost:3306,可省略;
user,password可以用“属性名=属性值”方式告诉数据库;
其他参数如:useUnicode=true&characterEncoding=GBK;
3>创建执行SQL的语句(Statement)
Statement st = conn.createStatement();
4>执行语句
ResultSet rs = st.executeQuery("select * from user");
5>处理执行结果(ResultSet)
while(rs.next())
{
System.out.println(rs.getObject(1)+"\t"+rs.getObject(2));
}
6>释放资源,非常重要,与创建顺序相反,数据库连接connection是非常稀缺的资源,消耗cpu,占内存很大,connection使用原则是尽量晚创建,尽量早关闭;
rs.close();
st.close();
conn.close();
3、规范的数据库连接方法:
static void template()
{
Connection conn = null;
Statement st = null;
ResultSet = null; //import java.sql.ResultSet,java标准库中的类;
try
{
// 1.注册驱动,工具类中实现
// 2.建立连接
conn = JdbcUtils.getConnection();
// 单例模式:
// conn = JdbcUtils.getInstance().getConnection();
// 3.创建语句
st = conn.createStatement();
// 4.执行语句
ResuleSet rs = st.executeQuery("select * from user");
// 5.处理结果
while(rs.next())
{
System.out.println(rs.getObject(1)+"\t"+rs.getObject(2));
}
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, st, conn);
}
}
//连接数据库工具类
public final class JdbcUtils
{
// 私有变量,初始化连接参数
private static String url = "jdbc:mysql://localhost:3306/jdbc";
private static String user = "root";
private static String password = "";
// 私有构造器
private JdbcUtils()
{
}
// 单例模式
/* private static JdbcUtils instance = new JdbcUtils();
public static JdbcUtils getInstance()
{
// 延迟加载
if(instance == null)
{
// 加锁解决并发问题,将类锁起再构造
synchronized(JdbcUtils.class)
{
// 双重检查,不可少
if(instance == null)
{
instance = new JdbcUtils();
}
}
}
return instance;
}*/
// 静态代码块,注册驱动
static
{
try
{
Class.forName("com.mysql.jdbc.Driver");
}
catch(ClassNotFoundException e)
{
throw new ExceptionInInitializerError(e);
}
}
// 静态方法,创建连接
public static Connection getConnection() throws SQLException
{
return DriverManager.getConnection(url, user,password);
}
// 静态方法,释放资源
public static void void free(ResulteSet rs, Statement st, Connection conn)
{
try
{
if(rs != null)
{
rs.close();
}
}
catch(SQLException e)
{
e.printStackTrace();
}
finally
{
try
{
if(st != null)
{
st.close();
}
}
catch(SQLException e)
{
e.printStackTrace();
}
finally
{
try
{
if(conn != null)
{
conn.close();
}
}
catch(SQLException e)
{
e.printStackTrace();
}
}
}
}
}
4、增删改查操作(CRUD):
增删改用Statement.executeUpdate来完成,返回整数(匹配的记录数),这类操作相对简单;
查询用Statement.executeQuery来完成,返回的是ResultSet对象,ResultSet中包含了查询的结果;
//查询操作
static void read() throws SQLException
{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
st = conn.createStatement();
// 4.执行语句
ResuleSet rs = st.executeQuery("select id,name,birthday,money from user");
// 5.处理结果
while(rs.next())
{
System.out.println(rs.getObject("id") + "\t"
+ rs.getObject("name") + "\t"
+ rs.getObject("birthday") + "\t"
+ rs.getObject("money"));
}
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, st, conn);
}
}
//增加操作
static viod create throws SQLException
{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
st = conn.createStatement();
// 4.执行语句
String sql = "insert into user(name,birthday,money) values('name1','1990-01-01','1000'), from user";
// 返回int值,代表有几行记录受影响
int i = st.executeUpdate(sql);
// 5.处理结果
System.out.println("i=" + i);
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, st, conn);
}
}
// 修改操作
static viod update throws SQLException
{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
st = conn.createStatement();
// 4.执行语句
String sql = "update user set money=money+10";
// 返回int值,代表有几行记录受影响
int i = st.executeUpdate(sql);
// 5.处理结果
System.out.println("i=" + i);
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, st, conn);
}
}
// 删除操作
static viod delete throws SQLException
{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
st = conn.createStatement();
// 4.执行语句
String sql = "delete from user where id>4";
// 返回int值,代表有几行记录受影响
int i = st.executeUpdate(sql);
// 5.处理结果
System.out.println("i=" + i);
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, st, conn);
}
}
4、SQL注入问题:
在SQL中包含特殊字符或SQL的关键字(如' or 1 or ')时Statement将出现不可预料的结果,如出现异常或查询结果不正确,可用PreparedStatement来解决。
public class SQLInject
{
static void read(String name) throws SQLException
{
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
st = conn.createStatement();
String sql = "select * from user where name= '" + name +"'";
// 4.执行语句
ResuleSet rs = st.executeQuery(sql);
System.out.println("sql:" + sql);
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, st, conn);
}
}
public static void main(String[] args) throws SQLException
{
read("' or 1 or '");
}
}
输出结果:sql:select * from user where name='' or 1 or '',并输出所有user表记录。
处理方法:Statement换成PreparedStatement预处理。
static void read(String name) throws SQLException
{
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
String sql = "select * from user where name=?";
ps = conn.createPreparedStatement(sql);
ps.setString(1, name);
// 4.执行语句
ResuleSet rs = ps.executeQuery();
//ResuleSet rs = ps.executeQuery(sql);调用Statement类的executeQuery,这里会直接执行语句,不会替换问号,导致报错。
System.out.println("sql:" + sql);
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, ps, conn);
}
}
PreparedStatement(继承自Statement)相对Statement的优点:
1>没有SQL注入问题;
2>Statement会使数据库频繁编译SQL,可能造成缓冲区溢出;
3>数据库和驱动可以对PreparedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效);
4>执行多条语句时,速度更快。
5、jdbc中的数据类型和日期问题:
jdbc.date类继承自util.date类
1>util.date转化为jdbc.date:
ps.setDate(new java.sql.Date(birthday.getTime()));
2>jdbc.date转化为util.date:
birthday = rs.getDate("birthday"); //输出:2010-12-06,jdbc.Date类中已经做了格式化操作;
birthday = new java.util.Date(rs.getDate("birthday").getTime()); //输出:Sat Dec 06 00:00:00 CST 2010
6、jdbc访问大段文本数据(clob):
varchar类型缺省45个字符,最大长度255个字符。超过255需要用大文本类型存储。
1>插入操作:
static void create() throws SQLException,IOException
{
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
String sql = "insert into clob_test(big_bit) values(?)";
ps = conn.createPreparedStatement(sql);
File file = new File("src/aa.text");
Reader reader = new BufferedReader(new FileReader(file));
ps.setCharacterStream(1, reader, (int)file.length);
// 4.执行语句
int i = ps.executeUpdate();
reader.close();
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, ps, conn);
}
}
2>读取操作:
static void create() throws SQLException, IOException
{
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
String sql = "select big_text from clob_test";
ps = conn.createPreparedStatement(sql);
// 4.执行语句
int i = ps.executeQuery();
// 5.处理结果
while(rs.next())
{
Clob clob = rs.getClob(1);
Reader reader = clob.getCharacterStream();
//Reader reader = rs.getCharacterStream(1);
File file = new File("bb.text");
Writer writer = new BufferedWriter(new FileWriter(file));
char[] buff = new char[1024];
for(int i = 0; (i = reader.read(buff))>0;)
{
writer.write(buff, 0, i);
}
writer.close();
reader.close();
}
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, ps, conn);
}
}
7、jdbc访问二进制类型的数据(blob,长度64kb):
1>插入操作:
static void create() throws SQLException
{
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
String sql = "insert into blob_test(big_bit) values(?)";
ps = conn.createPreparedStatement(sql);
File file = new File("src/aa.jpg");
InputStream in = new BufferedInputStream(new FileInputStream(file));
ps.setBinaryStream(1,in,(int)file.length);
// 4.执行语句
int i = ps.executeUpdate();
in.close();
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, ps, conn);
}
}
2>读取操作:
static void create() throws SQLException
{
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
// 2.建立连接
conn = JdbcUtils.getConnection();
// 3.创建语句
String sql = "select big_bit from blob_test";
ps = conn.createPreparedStatement(sql);
// 4.执行语句
int i = ps.executeQuery();
// 5.处理结果
while(rs.next())
{
InputStream in = rs.getBinaryStream(1);
File file= new File("aa_bak.jpg");
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
byte[] buff = new byte[1024];
for(int i = 0; (i = in.read(buff))>0;)
{
out.write(buff, 0, i);
}
out.close();
in.close();
}
}
finally
{
// 6.释放资源
JdbcUtils.free(rs, ps, conn);
}
}
8、数据类型:
几种特殊且比较常用的类型:
DATA,TIME,TIEMSTAMP---date,time,datetime
CLOB---text
存:ps.setCharacterStream(index,reader,length);
ps.setString(i,s); //i指第几个参数
取:reader = rs.getCharacterStream(i);
reader = rs.getClob(i).getCharacterStream();
String = rs.getString(i); //i指第几列
BLOB---blob
存:ps.setBinaryStream(i,inputStream,length);
取:rs.getBinaryStream(i);
rs.getBlob(i).getBinaryStream();
9、异常处理
10、工厂模式
11、事务
四大特性(ACID):
原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中一部分;
一致性(consistentcy):在事务处理前后,数据是一致的(数据库的数据完整性约束);
隔离性(isolcation):一个事务对另一个事务的影响;
持续性(durability):事务处理的效果能够被永久的保存下来;
connection.setAutoCommit(false); //启动事务
connection.commit(); //提交事务
connection.rollback(); //回滚事务
当只想撤销事务中的部分操作时,可使用SavePoint
SavePoint sp = connection.setSavePoint(); //设置保存点
connection.rollback(savePoint); //回滚到保存点
connection.commit();
JTA:
跨越多个数据源的事务,使用JTA容器实现事务;
分成两个阶段提交:
javax。transaction.UserTransaction tx = (UserTransaction)ctx.lookup("jdniName");
tx.begin();
//connection1,connection2可能来自不同的数据库
tx.commit();或tx.rollback;
12、隔离级别
作用:保证多线程并发读取数据时的正确性。
java中设置隔离级别:
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
一般无需自己设置隔离级别,因为每个数据的隔离基本实现都不一样,设置之后也不能保证得到自己想要的结果。
三种错误:
脏读:一个事务读取了另一个未提交的并行事务写的数据;
不可重复读:一个事务读取前面已经读取过的数据,发现数据已被另一个已提交的事务修改过;
幻读:一个事务再次读取数据,能够读取到另一个已提交事务增加的数据。
隔离级别正确性表:
√ 可能出现(每个数据库实现不一样,可能会有些许差别);× 不会出现;
隔离级别 脏读 不可重复读 幻读
读未提交(Read uncommitted) √ √ √
读已提交(Read committed) × √ √
可重复读(Repeatable read) × × √
可串行化(Serializable) × × ×
mysql查询数据库隔离级别:
select @@tx_isolation;
mysql设置数据库隔离级别:
set transaction isolation level read uncommitted;
mysql打开事务:
start transaction;
11、存储过程
CallableStatement(从PreparedStatement扩展而来)
使用方法如下:
CallableStatement cs = connection.prepareCall("{call psname(?,?,?)}");
cs.registerOutParameter(index,Types.INTEGER);
cs.setXXX(i,xxx);
cs.executeUpdate();
int id = cs.getInt(index);
12、其他几个API
获取主键:
PreparedStatement ps = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKey();
int id = rs.getInt(1);
批处理,可以大幅度提升大量增删改的速度:
PreparedStatement.addBatch();
PreparedStatement.executeBatch();
可滚动的结果集:
Statement st = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = st.executeQuery(sql);
rs.beforeFirst();
rs.afterLast();
rs.first();
rs.isFirst();
rs.last();
rs.isLast();
rs.absolute(9);
rs.moveToInsertRow();
可更新的结果集:
Statement st = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
rs.updateString("col name","new value");
rs.updateRow();
自动更新数据库记录后,读取的为更新后的新数据;此功能一般少用,将其查询与修改混合在一起操作,不利于模块化。
13、DatabaseMetaData和parameterMetaData
DatabaseMetaData:
DatabaseMetaData dbmd = connection.getMetaData();
通过DatabaseMetaData可以获得数据库的相关信息:数据库名称,版本,厂商,是否支持事务,是否支持某种事务隔离级别,是否支持动态结果集等;
ParameterMetaData:
ParameterMetaData pmd = preparedStatement.getParameterMetaData();
通过ParameterMetaData可以获得参数信息。
ResultSetMetaData:
ResultSetMetaData resultSetMetaDate = rs.getMetaData();
通过ResultSetMetaData可以获取结果集的信息,包括结果集的列名、列类型、列值等。
14、数据源和连接池
DataSource用来取代DriveManager来获取Connection;
一般DataSource会用一个连接池来缓存,可以大幅度提高数据库的访问速度;
通过Datasource获得的Connection都是已经被包裹的连接,不是原来驱动的连接,它的close方法已经被修改;
连接池可以理解为一个能够存放Connection的Collection;
我们的程序只和DataSource打交道,不会直接访问连接池;
使用dpcp的步骤:
导入三个包:
commons-dpcp.jar,commons-pool.jar,commons-collections.jar;
配置文件:
dbcp.properties,配置一些指定属性;
初始化数据源:
DataSource myDataSource = BasicDataSourceFactory.createDataSource(prop);
创建连接:
Connection connection = myDatasource.getConnection();
15、使用继承优化JDBC代码(模板模式)
优化查询语句,区分可变和不变的部分,sql和ResultSet的处理是变化部分,创建和释放资源是不变部分;
提取超类,将不变部分放入超类,变化部分留给子类实现;
16、Spring和JdbcTemplate、NamedParameterJdbcTemplate、SimpleJdbcTemplate
查询带有参数和行映射的方法:
public Object queryForObject(String sql, Object[] args, RowMapper rowMapper),使用自定义的RowMapper完成映射;
一个RowMapper的常用实现BeanPropertyRowMapper,该实现可将结果集转换成一个Java Bean(字段名与Java Bean属性名不符合规范,可用别名处理)。
public List query(String sql, Object[] args, RowMapper rowMapper),返回多个结果。
public int queryForInt(String sql),如:select count(*) from user,其他结果比如String可用queryForObject方法向下转型。
public Map queryForMap(String sql, Object[] args),返回Map类型。
public List queryForList(String sql, Object[] args)返回Map集合。
更新:
public int update(String sql, Object[] args)
插入数据并获得结果集:
public Object execute(ConnectionCallBack action)
JdbcTemplate中方法主要传递sql,和数组参数,其方法要求sql占位符和参数数组位置需要对应。
描述:1、在excute方法中可以传递一个ConnectionCallback回调接口,在接口方法中将获取connection对象,可以自定义进行操作。
2、结果集的封装可以使用spring的RowMapper接口对象,也可以使用rowBeanPropertyRowMapper,这个只需传递一个对象的class即可。
NamedParameterJdbcTemplate是对JdbcTemplate进行了封装,主要多了一层对参数的解析,sql使用特殊组合的占位符,参数主要使用map,这样sql的占位符和参数数据就不需要在顺序上一一进行对应。
关键点描述:SqlParameterSource ps = new BeanPropertySqlParameterSource(user);可以使用SqlParameterSource来传递一个对象,来对sql的占位符进行填值,
KeyHolder keyHolder = new GeneratedKeyHolder();来捕获生成的主键值。
SimpleJdbcTemplate的使用建立在JDK1.5版本之上,里面封装了一个NamedParameterJdbcTemplate,主要添加了支持变长参数。
需要调用NamedParameterJdbcTemplate中有而SimpleJdbcTemplate中没有的方法时,可以使用simple.getNamedParameterJdbcOperations()。
获取JdbcTemplate中方法simple.getJdbcOperations()。