`
im127im
  • 浏览: 14098 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

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;
  }
  }
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics