jdbc的那点小事
jdbc的那点小事
2011年01月20日
1.Class.forName(),是根据类的名字将类装载到虚拟机里面了。把类装载到虚拟机里面和创建一个类的实例是不一样的,创建一个实例就会有一个
实例产生,但是把类装载到虚拟机里面就不一定会有实例产生。
2.通过DriverManager.registerDriver()和System.setProperty()方式,会直接将驱动放入驱动列表里面。
3.通过Class.forName()方式,是将类加载到虚拟机里面,存在在虚拟机中的类的静态代码块会立即被虚拟机执行,所有的数据库开发商开发的驱动
类Driver里面都有一段相同的代码(因为要遵循sun的标准),代码块如下:
public class Driver extends NonRegisterDriver implements java.sql.Driver{
static {
try{
java.sql.DriverManager.registerDriver(new Driver());
}catch(SQLException e){
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException{
}
}
4.注册驱动是使用Class.forName()更好点,因为这样也可以注册一个驱动,而使用DriverManager.registerDriver()时,我们的参数是
new 了一个驱动对象,但是数据库厂商提供的驱动类里面的静态代码块依然会执行,这样就相当于产生了两个驱动类实例,虽然对
程序没有影响,但是Class.forName()形式的更好点。
5.如果我们的程序中没有导入数据库驱动jar包,那么DriverManager.registerDriver()形式就不能通过编译,因为我们 new 了一个对象
作为参数,所以这样就依赖于数据库驱动 jar 包,即:无法导入Driver类,而且更换数据库时也要修改程序代码,而Class.forName()
和System.setProperty()形式则可以编译,因为他们的参数都是字符串,没有依赖外部的数据库类,只有当程序运行时,实例化类时,
才报异常。更换数据库时不需要更改代码,只要更改属性文件即可。
6.JDBC url是跟着数据库变动的,格式为: JDBC:子协议:子名称//主机名:端口号/数据库名?属性名=属性值&...
mysql数据库的url: jdbc:mysql://localhost:3306/mydb 此url没有子名称,oracle就有,jdbc是数据库url的协议,类似万维网上的
http或者文件上传中的ftp等。如果我们使用的主机名和端口号都是默认的,则可以不写,例如上面的url也可以写成jdbc:mysql:///mydb
7.数据库连接(Connection)是非常稀有的资源用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用
原则是尽量晚创建,尽量早的释放。
8.当我们取得从数据库中获得的值时有两种方式的参数,一种是所以序列号,一种是字段列名,如果按照索引序列号则必须按照数据库中列的
顺序获得值,如果是按照字段列名则与顺序无关,可以任意顺序取。
9.sql注入时,我们只要输入一个 ' or 1 or' 即可。例如:
String name = "' or 1 or '"; //这两个单引号是要与sql语句中的前后两个单引号进行匹配的
sql = "select * from t_user where name ='" + name + "'";
组成的sql语句为:
select * from t_user where name =''or 1 or''
10.使用PreparedStatement和Statement的区别:
PreparedStatement会预处理sql语句,同时也可以屏蔽掉一下特殊字符,防止sql注入。其次,如果同一个sql执行次数比较多时PreparedStatement
比Statement的效率高,如果同一个Sql执行的次数比较少则效率Statement比PreparedStatement高。
11.PreparedStatement(从Statement扩展而来)相对Statement的优点:
1.没有SQL注入问题。
2.Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
3.数据库和驱动可以对PreparedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)
12.处理大文本(即:把一个文件【中的内容】存入数据库):
String sql = "insert into clob_test(big_test) values(?)";
ps = conn.prepareStatement(sql);
File file = new File("src/cn/itcast/DBUtil3.java");
Reader reader = new BufferedReader(new FileReader(file));
ps.setCharacterStream(1,reader,(int)file.length()) ;
//ps.setString(1,x); //可以将文件内容构建成一个String后存到数据库中,String没有大小限制,但是数据库中要定义成clob
int i = ps.executeUpdate();
13.获取数据库中的大文本类型:
Clob clob = ResultSet.getClob(1);
Reader reader = clob.getCharacterStream();
//reader = ResultSet.getCharacterStream(1);
//String s = rs.getString(1);
File file = new File("DBUtil3_bak.java");
Writer writer = new BufferedWriter(new FileWriter(file));
char[] buff = new char[1024];
for(int i = 0;(i = reader.read(buff)) > 0;){
writer.writer(buff,0,i);
}
14.处理二进制文件(例如:图片)
String sql = "insert into blog_test(big_bit) values(?)";
ps = conn.prepareStatement(sql);
File file = new File("IMG_001.jpg");
InputStream in = new BufferedInputStream(new FileInputStream(file));
ps.setBinaryStream(1,in,(int)file.length());
ps.executeUpdate();
15.读取Blob数据
//Blob blob = rs.getBlob(1);
//InputStream in = blob.getBinaryStream();
InputStream in = rs.getBinaryStream(1);
File file = new File("IMG_002.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);
}
16.一旦Connection关闭(断开)了,那么ResultSet中的数据也随即消失。所以当我们访问数据库时得到的ResultSet需要及时
处理,不能继续往上传递,除非我们不关闭Connection。即:在使用ResultSet之前不能关闭Connection连接。所以
Connection一旦关闭(断开)Statement和ResultSet就无效了。
17.异常处理:
从JDBC连接代码的那个文件中将异常往上抛,跑到调JDBC代码的DAO层时将异常捕获,这时捕获到的异常为SQL异常,我们不能
再将这个SQL异常继续往上跑,那样会污染我们service层的代码,所以我们要在DAO层将这个SQL异常装换为运行时异常进行处
理,代码如下:
自定义异常类:
public class DaoException extends RuntimeException {
public DaoException() {
}
public DaoException(String message) {
super(message);
}
public DaoException(Throwable cause) {
super(cause);
}
public DaoException(String message, Throwable cause) {
super(message, cause);
}
}
捕捉异常的DAO层代码:
public User findUser(String id) {
try {
String sql = "";
conn = dbutil.getInstance().getConnection();
state = conn.createStatement();
rs = state.executeQuery(sql);
while(rs.next()){
u = new User();
}
} catch (SQLException e) {
throw new DaoException(e.getMessage(),e);
}finally{
dbutil.free(rs, state, conn)
}
return u;
}
18.动态切换DAO层代码
使用工厂方法模式替换Dao层实现类:
属性文件中:src/daoconfig.properties
userDaoClass=com.java_min.DaoImpl.UserDaoImpl
工厂类中:
public class DaoFactory{
private UserDao userDao = null;
private static DaoFactory instance = new DaoFactory();
private DaoFactory(){
Properties prop = new Properties();
InputStream in = new FileInputStream(new File("src/daoconfig.properties"));
prop.load(in);
String userDaoClass = prop.getProperty("userDaoClass");
userDao = (UserDao)Class.forName(userDaoClass).newInstance() ;
}
public static DaoFactory getInstence(){
return instance;
}
public UserDao getUserDao(){
return userDao;
}
}
总结:工厂一般都是用单例的,其次我们用实现DAO层的动态切换就用使用读取属性文件的方式,读取
实现类的全路径表示,后使用反射机制得到DAO层实现类的实例,此处读取属性文件的方式有
两种多种方式,上面是使用了文件流对象读取属性文件,我们还可以使用"类加载器"读
取属性文件,代码入:
Properties prop = new Properties();
InputStream in = DaoFactory.class.getClassLoader().getResourceAsStr eam("daoconfig.properties");
prop.load(in);
"类加载器"是每个类都有的,它可以加载"类.class"还可以加载其他东西,如上面的属性文件,文件流与类加载器加载
属性文件的不同之处在于,文件流加载时,需要给出属性文件的路径,当使用类加载器加载时我们不需要给出属性文件
的路径,只需要将属性文件放入classpath目录下即可,类加载器会从classpath目录下寻找的。
19.事物保存点处理:
有事事务回滚时,我们我们并不想让所有的事务都回滚,而是回滚到某个点上。可以使用事务保存点。代码如下:
Savepoint sp;
try{
String sql = "";
state.executeUpdate(sql);
sp = conn.setSavepoint(); 1.
sql = "";
state.executeUpdate(sql);
sql = "";
rs = state.executeQuery(sql);
while(rs.next()){
throw new RuntimeExcetion();
}
}catch(Exception){
if(conn != null && sp != null){
conn.rollback(sp); //如果不带参数就会全部回滚,带了参数就会回滚到此参数指定的事务保存点上
conn.commit();
}
}finally{
if(conn != null){
conn.rollback();
}
}
以上就会回滚到1.出,只有第一条语句执行成功提交,后面的那个更新被回滚。
20.数据库隔离级别(假如有两个人A、B同时修改数据库,两个人操作数据库的时候就是个子开启个子的事务,两个事务互不干扰,即:隔离性):
未提交读:假如A修改了数据库中的数据,但是还没有提交事务,这是B去读数据也能读到刚才A修改的数据,如果A此时回滚了事务,那么B读取的这个数据就是脏数据了。
提交读:假如A修改了数据库中的数据,但是还没有提交事务,这是B去读数据是读不到A刚才修改的数据的,如果A此时提交了事务,那么B就可以读取这个数据了。
可重复读:假如A读取了数据库总的数据,此时B在A读取了数据后修改了数据库中的数据,也提交了事务,那么A读取的数据是不会改变的,如果A再重新读取数据也是读取不到的,
除非A从新开启一个事务就可以读取B修改后的数据了。
序列化:就是相当于数据库加锁,如果A开启事务操作数据时(包括读取数据),那么B随后也开启事务操作数据,如果B提交事务时是不能提交事务的,那么事务会停下来等待A的
事务提交,如果A不提交事务,B的操作就会一直处于等待状态,一旦A提交了事务,那么B的事务马上就会结束等待,执行事务提交。
21.JDBC调用存储过程:
存储过程:
DELIMITER $$
DROP PROCEDURE IF EXISTS 'jdbc'.'addUser' $$
CREATE PROCEDURE 'jdbc'.'addUser'(in pname varchar(45),in birthday date,in money float,out pid int)
BEGIN
insert into user(name,birthday,momey) values(pname,birthday,money);
select last_insert_in() into pid; //last_insert_in 这是数据库中的一个函数,用于查询当前线程插入的数据的索引号
END $$
DELIMITER ;
java调用存储过程:
String sql = "{call addUser(?,?,?,?)}";
cs = conn.prepareCall(sql);
cs.registerOutParameter(4,Types.INTEGER);
cs.setString(1,"simier");
cs.setDate(2,new java.sql.Date(System.currentTimeMillis()));
cs.setFloat(3,100f);
cs.executeUpdate();
int id = cs.getInt(4);
22.批处理:
PreparedStatement:
String sql = "insert into t_user values(?,?)";
ps = conn.prepareStatement(sql);
for(int i = 0;i 内存分页,效率比较低,这种方式可以在数据库不支持数
据库分页时使用这种方法)。
rs.absolute(100);
int i = 0;
while(rs.next() && i 调用对象的方法,代码如下:
static void invoke(Object obj){
Method[] ms = obj.getClass().getMethods(); //获得该类的所有方法,以及超类中的方法
Method[] m = obj.getClass().getDeclaredMethods(); //获得自定义的所有方法,超类和私有的方法无法获得
for(Method i:m){
System.out.println(i);
}
}
3.使用反射机制精确调用对象的方法,代码如下:
static void invoke(Object obj){
Method m = obj.getClass().getMethod(methodName,null); //methodName是想要调用的方法的名字,null表示该方法不需要传入参数
m.invoke(obj,null); //执行该方法,obj必须是实例化的对象,这个方法接受的参数都必须是对象
}
3.使用反射机制额调用对象的属性,代码如下:
static void field(Class clazz){
Field fs = clazz.getDeclaredFields(); //得到该类的所有自定义属性
fs = clazz.getField(); //得到该类的所有public类型属性
}
26.编写一个基本的连接池实现连接的复用,代码如下:
package com.java_min.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
//连接池类
public class MyDataSource {
//此段代码可以放到JDBCUtil类中,当创建连接池对象时,以参数的形式传进来
/*private static String url = "jdbc:mysql://localhost:3306/jdbc";
private static String userName = "root";
private static String password = "root";
private static String driver = "com.mysql.jdbc.Driver";*/
private String url;
private String userName;
private String password;
private static int initCount = 5;
private static int maxCount = 10;
private int currentCount = 0;
private LinkedList connectionPool = new LinkedList();
//为了灵活性,此段代码也放在JDBCUtil类中
/*static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
new ExceptionInInitializerError(e);
}
}*/
public MyDataSource(String userName,String password,String url){
this.userName = userName;
this.password = password;
this.url = url;
try {
for(int i = 0;i 0){
return this.connectionPool.removeFirst();
}
if(this.currentCount API进行了很好的封装这个类就像我们自己
对JDBC进行封装一样,只是代码更健壮和功能更强大而已,我们以后在实际项目中可以使用JdbcTemplate类来完全
替代直接使用JDBC API,这与直接使用JDBC API没太大的性能区别,使用JdbcTemplate类需要额外从Spring开发包
中导入spring.jar和commons-logging.jar包。
org.springframework.jdbc.core.JdbcTemplate对象介绍:
构造JdbcTemplate对象时要给其传一个数据源类型的参数,有两种方式,构造对象时传入一个参数,或者构造完后使
用对象的setter方法将数据源设置进去。代码如下:
1. JdbcTemplate jt = new JdbcTemplate(org.apache.commons.BasicDataSource);
2. JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dataSource);
JdbcTemplate对象API介绍:
1. jt.queryForObject(sql,args,rowMapper);
sql:为我们传入的预sql语句
args:为一个Object类型的数组,为填充我们传入的预sql语句的占位符
rowMapper:为一个行映射器接口
示例代码如下:
String sql = "select id,name,money from t_user where name=?";
Object[] args = new Object[]{name};
Object user = jt.queryForObject(sql,args,new RowMapper(){
public Object mapRow(ResultSet rs,int rowNum)throws SQLException{ //匿名内部类,回调函数
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setMoney(rs.getFloat("money"));
return user;
}
});
return (User)user;
2. Object user = jt.queryForObject(sql,args,argTypes,new BeanPropertyRowMapper(Class)); //只能返回一个结果,如果有多个结果该方法就会报异常
sql:我们传入的预处理sql语句
args:为一个Object类型的数组,用来填充我们预处理sql的占位符的
argTypes:用来定义传入的参数的类型,此参数可选,如果不选spring会根据反射机制获得每个传入的参数的类型
BeanPropertyRowMapper:此类实现了RowMapper接口,可以用来代替上面那个回调方法。但是使用此对象的时候
有一些限制条件,构造BeanPropertyRowMapper对象时必须要传入要查询对象的类,
之后BeanPropertyRowMapper对象会使用反射机制将查询出来的结果赋值到对应的对象
属性上去,所以此处条件为,java类中的属性必须于数据库中的字段同名,或者命名单
词相同,彼此都符合java和数据可的规范,如果java类中的属性命名与数据库中的字段
名不相同,那么可以在sql语句中使用别名的方式使之与java类中的属性同名。
示例代码如下:
JdbcTemplate jt = new JdbcTemplate(org.apache.commons.BasicDataSource);
String sql = "select id,user_name,money,user_birthday_date as birthday from t_user where name=?";
Object[] args = new Object[]{name};
//int[] argTypes = new int[]{Types.INTEGER};
//Object user = jt.queryForObject(sql,args,argTypes,new BeanPropertyRowMapper(User.class)); //加不加输入参数的类型定义,效果一样,不加Spring也会通过反射机制获得参数类型的
Object user = jt.queryForObject(sql,args,new BeanPropertyRowMapper(User.class));
return (User)user;
java代码:
private String id;
private String userName;
private float money;
private Date birthday;
总结,java类里面的属性一定要大于或者等于sql语句中的属性的数量。
3. List users = jt.query(sql,args,new BeanPropertyRowMapper(User.class)); //可以返回多个结构
sql:我们传入的预处理sql语句
args:为一个Object类型的数组,用来填充我们预处理sql的占位符的
BeanPropertyRowMapper:此类实现了RowMapper接口,可以用来代替上面那个回调方法。但是使用此对象的时候
有一些限制条件,构造BeanPropertyRowMapper对象时必须要传入要查询对象的类,
之后BeanPropertyRowMapper对象会使用反射机制将查询出来的结果赋值到对应的对象
属性上去,所以此处条件为,java类中的属性必须于数据库中的字段同名,或者命名单
词相同,彼此都符合java和数据可的规范,如果java类中的属性命名与数据库中的字段
名不相同,那么可以在sql语句中使用别名的方式使之与java类中的属性同名。
示例代码如下:
JdbcTemplate jt = new JdbcTemplate(org.apache.commons.BasicDataSource);
String sql = "select id,user_name,money,user_birthday_date as birthday from t_user where id :m and id :money and id 不能使用占位符?必须使用与JavaBean中属性同名的命名参数
SqlParameterSource ps = new BeanPropertySqlParameterSource(user); //此处参数user为User对象
KeyHolder keyHolder = new GeneratedKeyHolder();
named.update(sql,ps,keyHolder); //此方法更新(插入)结果后,会返回所更新(插入)的记录的主键值,并将其放入keyHolder对象中
int id = keyHolder.getKey().intValue(); //如果是双主键可以使用方法getKeys(),该方法会返回一个Map对象,Map的key的值是表中字段的名字,value是表中字段的值
return id;
33.使用SimpleJdbcTemplate和泛型技术简化代码(JDK1.5),代码如下:
public class SimpleJdbcTemplateTest{
static SimpleJdbcTemplate simple = new SimpleJdbcTemplate(org.apache.commons.BasicDataSou rce);
static User find(String name){
String sql = "select id,name,money,birthday from t_user where name=? and money=?";
User user = simple.queryForObject(sql,ParameterizedBeanPropert yRowMapper.newInstance(User.class),name,100f); //此方法返回的类型有newInstance的参数决定
return user;
}
//为了更大的灵活性,以上查询方法可用以下代码(泛型)替换
static T find(String name,Class clazz){
String sql = "select id,name,money,birthday from t_user where name=? and money=?";
Object obj = simple.queryForObject(sql,ParameterizedBeanPropert yRowMapper.newInstance(clazz),name,100f); //此方法返回的类型有newInstance的参数决定
return obj;
}
}
猜你喜欢
转载自im127im.iteye.com/blog/1362806
今日推荐
周排行