目录
一.JDBC
二.原生JDBC开发的步骤
三.定义原生JDBCUtils.java工具类
四.DBCP连接池
五.C3P0连接池
六.DBUtils编写语句
介绍:java语言操作各种数据库的技术
* JDBC由一堆接口和类组成
* 类: DriverManger,用于注册驱动
* 接口:
* Connection:数据库连接类的根接口
* Statement:数据库语句执行类的根接口
* ResultSet:结果集类的根接口
ps: 以上接口的实现类,在驱动包中。接口都在java.sql.xxx下。
0.向工程中导入具体的数据库驱动包(jar包)
1.注册驱动
* DriverManger.register(new com.mysql.jdbc.Driver());//这样会注册两次
* Class.forName("com.mysql.jdbc.Driver");ps:第一次会在类加载的时候会在静态代码块执行一次。
2.获取连接(四大要素:驱动,连接串,用户名,密码)
* 前提:必须有驱动
* Connection conn = DriverManager.getConnection(
* "jdbc:mysql://ip地址:3306/数据库名","用户名","密码"
* );
3.获取sql语句执行对象
* Statement st = conn.createStatement();
* PreparedStatement pst = conn.prepareStatement(String sql);
* pst.setObject("占位符序号","具体的参数值");
ps:直接的conn.createStatement因为字符没转义如果不使用prepareStatement进行预处理,可能会受到sql注入攻击。
4.使用执行对象执行sql语句,获取到结果集
* ResultSet rs = st.excuteQuery(sql);
* ResultSet rs = pst.excuteQuery();
5.处理结果集
* rs.next();//判断有没有下一条记录
* rs.getObject("字段名");//rs.getObject(字段对应的序号);
6.释放资源
* rs.close(),st.close,conn.close()
定义工具类:
- 首先获取配置文件,然后加载驱动。
- 创建数据库连接的静态成员方法。
- 创建关闭数据库资源的静态成员方法。
public class GJDBCUtils { //这个工具类,主要为我们获取一个数据库连接 private static String driverName = null; private static String url = null; private static String username = null; private static String password = null; //静态代码块,目的,让第一次使用到JDBCUtils中加载驱动,第二次以后不再加载了 static{ //1.加载驱动 try { //0.获取配置文件 Properties ps = new Properties(); ps.load(new FileInputStream("gjdbc_config.properties")); driverName = ps.getProperty("driverName"); url = ps.getProperty("url"); username = ps.getProperty("username"); password = ps.getProperty("password"); Class.forName(driverName); } catch (Exception e) { // TODO Auto-generated catch block //System.out.println("驱动加载失败..请检查驱动包"); throw new RuntimeException("驱动加载失败..请检查驱动包"); } } public static Connection getConnection() throws Exception{ //2.获取和数据库的连接 Connection conn = DriverManager.getConnection(url, username, password); //3.返回连接对象 return conn; } //关闭所有资源的统一代码 public static void closeAll(Connection conn,Statement st,ResultSet rs){ //负责关闭 if(conn != null){ try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(st != null){ try { st.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(rs != null){ try { rs.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
进行查询操作:
- 使用静态方法进行连接。
- 创建statement对象,并执行查询语句。(一般为了避免sql注入,会预处理sql语句)
//查询 public static void query(){ //1. Connection conn = null; //2. Statement st = null; //3. ResultSet rs = null; //4. try{ conn = JDBCUtils.getConnection(); st = conn.createStatement(); rs = st.executeQuery("select * from category where cid= 14"); while(rs.next()){ Object cid = rs.getObject("cid"); Object cname = rs.getObject("cname"); System.out.println(cid+"\t"+cname); } }catch(Exception e){ System.out.println(e); }finally { JDBCUtils.closeAll(conn, st, rs); } }
预防sql注入的修改:
String sql = "select * from users where username = ? and password = ?"; st = conn.prepareStatement(sql); //设置值 st.setObject(1,username); st.setObject(2, password); rs = st.executeQuery();
ps:防止SQL注入,内部会自动对sql语句进行转译,让这个sql语句中的变量和sql语法有关的字符都失效。
ps:PreparedStatement: 他是Statement的子类。
使用方式(推荐):
* 不需要解析dbcpconfig.properties文件
* 用到一个核心类: BasicDataSourceFactory
* DataSource ds = BasicDataSourceFactory.createDataSource(Properties对象);
* 然后直接使用
* ds.getConnection();
补充:加载配置文件的两种方式:
* 1.配置文件在工程的根目录
* Properties ps = new Properties();
* ps.load(new FileInputStream("配置文件名"));
* 2.配置文件在src根目录
* Properties ps = new Properties();
* InputStream in = 当期类.class.getClassLoader().getResourceAsStream("配置文件名");
* ps.load(in);public class DBCPUtils02 { private static DataSource ds = null; //静态代码块,设置ds的四大要素 static{ try { Properties ps = new Properties(); // ps.load(new FileInputStream("dbcpconfig.properties")); ps.load(DBCPUtils02.class.getClassLoader().getResourceAsStream("dbcpconfig.properties")); ds = BasicDataSourceFactory.createDataSource(ps); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static Connection getConnection() throws SQLException{ //返回一个连接对象,不要用DriverManager获取,而是连接池中获取 return ds.getConnection(); } ....... }
使用方式:基于XML配置文件
* 注意:
* 1.文件名和文件中标签名 必须是官方指定的
* 2.必须放到src根目录下,C3P0的实现类ComboPooledDataSource会自动加载
* 使用
* ComboPooledDataSource ds = new ComboPooledDataSource();
* //不需要设置四大要素,因为ds会自动去加载 src下一个叫做次c3p0-config.xml的文件
* ds.getConnection();public class C3P0Utils02 { private static ComboPooledDataSource ds = new ComboPooledDataSource(); public static Connection getConnection() throws SQLException{ //获取连接,不要自己去DriverManager获取,而是从C3P0连接池获取 return ds.getConnection(); } ....... }
DBUtils:主要用于 关闭连接,释放资源,和事务相关。
QueryRunner: 主要用于 增删改查 CURD
* int update(String sql,Object... params);//用于增删改
* 不一定 query(String sql,ResultSetHandler接口实现类对象 ,Object...params);//用于查询的
ResultSetHandler:* (Object[])ArrayHandler:把结果集中的第一条记录,封装成Object[],返回这个Object数组
* (List把结果集中每一条记录,封装成Object数组,把这个数组再封装到集合中,并返回集合
* (JavaBean)BeanHandler:把结果集中的第一条记录,封装到JavaBean对象,并返回这个对象
* (List)BeanListHandler :把结果集中每一条记录,分别封装到JavaBean对象保存到集合中,返回这个集合
* (List:把结果集中的某一列的值,封装到list集合中,并返回
* (Map)MapHandler :把结果集中的第一条记录,封装到Map中。
* (List:把结果集中的每一条记录,封装到Map中。
* (Object)ScalarHandler:主要用来保存单一数据//ArrayHandler处理类的使用 public static void demo01() throws SQLException{ //1.创建QueryRunner对象 QueryRunner qr = new QueryRunner(C3P0Utils02.getDataSource()); //2.执行查询 String sql = "select * from category"; Object[] objs = qr.query(sql, new ArrayHandler()); //3.打印 for (Object obj : objs) { System.out.println(obj); } } //ArrayListHandler处理类的使用 public static void demo02() throws SQLException{ //1.创建QueryRunner对象 QueryRunner qr = new QueryRunner(C3P0Utils02.getDataSource()); //2.执行查询 String sql = "select * from category"; List
数据库分层逻辑:
\utils\C3P0Utils02 .java ... ConnectionManager.java
连接池获取连接 并且 定义连接池的逻辑
\dao\AccountDao.java
定义数据库查询逻辑
\service\AccountService.java
定义业务服务逻辑
\view\AccountView.java
定义web请求的服务
-----------------------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------------------
\utils\C3P0Utils02 .java
连接池获取连接
\utils\ConnectionManager.java
负责获取连接,开启事务,提交事务,回滚事务,关闭连接
定义连接池的逻辑
原理:存放的value是当前线程中是要共享的数据。
作用:使用ThreadLocal来管理连接,保证同一个线程为同一个连接。
ps:如果不为同一个连接(相当于不同的用户)操作,会出bug的,必须保证同一个连接。
public class ConnectionManager { //1.定义一个集合 ThreadLocal 对象来保存当前线程的连接 private static ThreadLocal
tl = new ThreadLocal (); //2.获取连接 public static Connection getConnection() throws SQLException{ //1.先从tl中获取连接 Connection conn = tl.get(); //2.判断conn是否为空 if(conn == null){ //说明 是service层第一次获取 conn = C3P0Utils02.getConnection(); tl.set(conn); } //如果不为空 说明 是dao层第二次以后获取 return conn; } //3.开启事务 public static void start() throws SQLException{ ConnectionManager.getConnection().setAutoCommit(false); } //4.提交事务 public static void commit() throws SQLException{ ConnectionManager.getConnection().commit(); } //5.回滚事务 public static void rollback() throws SQLException{ ConnectionManager.getConnection().rollback(); } //6.关闭连接 public static void close() throws SQLException{ ConnectionManager.getConnection().close(); } } \dao\AccountDao.java
定义数据库查询逻辑
public class AccountDao { //转账出去 //jack 转出去 1000元 public void fromAccount(String fromName,double money) throws SQLException{ Connection conn = ConnectionManager.getConnection(); //1.创建QueryRunner对象 QueryRunner qr = new QueryRunner(); //2.执行减钱操作 qr.update(conn,"update account set money=money-? where name=?", money,fromName); } //收钱回来 public void toAccount(String toName,double money) throws SQLException{ Connection conn = ConnectionManager.getConnection(); //1.创建QueryRunner对象 QueryRunner qr = new QueryRunner(); //2.加钱操作 qr.update(conn,"update account set money=money+? where name=?",money,toName); } }
\service\AccountService.java
定义业务服务逻辑
public class AccountService { //转账业务 public void transfer(String fromName,String toName,double money){ //直接调用dao AccountDao dao = new AccountDao(); try { //开启事务 ConnectionManager.start(); //转出去 dao.fromAccount(fromName, money); //模拟异常 System.out.println(1/0); //拿回来 dao.toAccount(toName, money); //提交事务 ConnectionManager.commit(); System.out.println("转账成功!!!"); } catch (Exception e) { // TODO Auto-generated catch block System.out.println("程序出现异常,程序回滚"); //回滚事务 try { ConnectionManager.rollback(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } }finally { try { ConnectionManager.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
\view\AccountView.java
定义web请求的服务
public class AccountView { public static void main(String[] args) { //模拟用户转账,输入付款人的姓名,收款人的姓名,转账金额 String fromName = "jack"; String toName = "tom"; double money = 5000; AccountService service = new AccountService(); service.transfer(fromName, toName, money); } }
事务特性:
- 原子性:强调事务的不可分割.多条语句要么都成功,要么都失败。
- 一致性:强调的是事务的执行的前后,数据要保持一致.
- 隔离性:一个事务的执行不应该受到其他事务的干扰.
- 持久性:事务一旦结束(提交/回滚)数据就持久保持到了数据库.
如果不考虑事务的隔离性,会引发一些安全性问题:
* 脏读 :一个事务读到另一个事务还没有提交的数据.
* 不可重复读 :一个事务读到了另一个事务已经提交的update或者delete的数据,导致在当前的事务中多次查询结果不一致.
* 虚读/幻读 :一个事务读到另一个事务已经提交的insert的数据,导致在当前的事务中多次的查询结果不一致.
解决引发的读问题:(设置事务的隔离级别)
* 1 read uncommitted :未提交读.脏读,不可重复读,虚读都可能发生.
* 2 read committed :已提交读.避免脏读.但是不可重复读和虚读有可能发生.(Oracle默认)
* 4 repeatable read :可重复读.避免脏读,不可重复读.但是虚读有可能发生.(MySql默认)
* 8 serializable :串行化的.避免脏读,不可重复读,虚读的发生.
总结:
事务的并发安全需要隔离级别的控制。
连接一致确保了单事务的安全。