一.JDBC 连接数据库
-
JDBC的本质
JDBC本质是一个java类,能够实现sun公司提供的一套接口规范。如 java.sql.Driver java.sql.Connection java.sql.Satatement
-
JDBC连接数据库的7大步骤
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; /** * 七大步骤 * 1)导包驱动包 * 2)注册驱动--加载驱动类 * 3)获取数据库的连接对象java.sql.Connection * 4)准备sql语句 * 5)通过Connection连接对象获取数据库的执行对象 * 6)执行sql语句 * 7)释放资源 */ public class JdbcCRUD { public static void main(String[] args) throws Exception{ // 1)导包驱动包 // 2)注册驱动--加载驱动类 // 3)获取数据库的连接对象java.sql.Connection Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root"); // 4)准备sql语句 String sql = "select * from stu"; // 5)通过Connection连接对象获取数据库的执行对象 Statement stmt = conn.createStatement(); // 6)执行sql语句 ResultSet res = stmt.executeQuery(sql); //输出 while (res.next()){ System.out.println(res.getInt("id")+ res.getString("name")+res.getString("sex") + res.getString("address") ); } // 7)释放资源 res.close(); stmt.close(); conn.close(); } }
-
封装工具类
import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class JdbcUtils { //成员变量声明三个变量 private static String url = null ; private static String user = null ; private static String password = null ; private static String driverClass = null ; //模拟 驱动jar包---Driver驱动类---提供静态代码块 static{ try { //想办法获取这些参数---->提供配置文件 后缀名.properties---->放在 //src下面 //1)读取配置文件内容 InputStream inputStream = JdbcUtils.class.getClassLoader() .getResourceAsStream("jdbc.properties"); //2)创建一个属性集合列表Properties Properties prop = new Properties() ; //System.out.println(prop) ;//测试 ---肯定空列表 //3)将1)获取资源文件的输入流对象---加载到属性集合列表中 prop.load(inputStream); // System.out.println(prop) ; //测试--->有属性列表内容 //4)通过key获取value driverClass = prop.getProperty("driverClass"); url = prop.getProperty("url") ; user = prop.getProperty("user") ; password = prop.getProperty("password") ; //5)加载驱动类 Class.forName(driverClass) ; } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } private JdbcUtils(){} /** * 获取数据库的连接对象 * @return */ public static Connection getConnection(){ try { //需要驱动管理DriverManager获取连接对象 Connection connection = DriverManager.getConnection(url, user, password);//获取这三个参数的内容 return connection ; } catch (SQLException throwables) { throwables.printStackTrace(); } return null ; } /** * 释放资源,针对DQL语句操作释放的相关资源对象 * @param rs * @param stmt * @param conn */ public static void close(ResultSet rs,Statement stmt,Connection conn){ if(rs!=null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(stmt!=null){ try { stmt.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn!=null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } /** * 释放资源,针对DDL语句,DML语句 * @param stmt * @param conn */ public static void close(Statement stmt,Connection conn){ close(null,stmt,conn); } }
-
PreparedStatement预编译对象的操作步骤
PreparedStatement原生的写法 1)注册驱动 2)获取数据库的连接对象 3)准备参数化sql语句,使用占位符号? 4)通过数据库的连接对象 获取预编译对象,同时将sql发送给数据库,(参数类型,第几个参数)都会存储在预编译对象中 5)给参数赋值 6)执行预编译的sql 7)释放资源 1.创建数据库连接对象 Connection con = DruidJdbcUtils.getConnnection(); 2.准备参数化SQL语句 String sql = "select * from table_name where id = ?"; 3.获取SQL预编译执行对象,同时发送参数化SQL到数据库进行编译 PrepareStatement stmt = conn.preparedStatemnet(sql); 4.参数赋值 stmt.setInt(1,5); 5.执行查询 ResultSet re = stmt.executeQuery(); 6.遍历结果集 while(re.next()){ re.getInt("id"); }
-
Statement和PreparedStatement预编译的区别
1. 效率方面 Statement对象:每次书写一条sql就需要通过Statment将sql语句发送给数据库,效率低;同时,会对数据库造成很大压力。 PrepareStatement对象:将一条参数化的sql语句发送给数据库,进行预编译,将编译结果存储在预编译对象中,下一次直接赋值,且可以赋值多次,发送一次sql,执行不同的参数 2. 安全方面 Statement对象:发送的sql语句存在字符串拼接,会出现安全问题(SQL注入,恶意攻击数据库,造成安全漏洞) PrepareStatement对象:参数化的sql语句不存在字符串拼接,有效防止SQL注入,开发中使用PreparedStatement来对数据库CRUD,比Statement安全。
-
连接池
1. 原生写法的弊端 1)会造成Connection连接对象浪费 2)频繁在创建数据库连接对象,执行效率低 2. 通过连接池解决原生写法的弊端 sun公司提供一个接口 javax.sql.DataSource代表 DriverManger驱动管理类,是一个物理数据源工厂! 通过连接池的方式实现javax. sql. DataSource接口---里 面的getConnection()方法 1)这个jar包--数据厂商提供druid版本号.jar 2)准备连接池的配置文件-->配置相关连接池的参数 connectionPool连接池存放一些 初始化连接对象的数量以及 最大连接数量
-
连接池的作用
1. 资源重复利用 使用连接池,避免了频繁的创建连接对象,使用完毕关闭后,造成资源销毁大 2. 提高系统的响应速度 在程序启动时,提前准备了足够的连接对象,储存在连接池中,当用户访问较多时,可以直接冲连接池中取连接对象,执行速度快. 3. 控制连接对象 多个连接对象被多个线程在同一时刻使用的时候,连接池中对连接对象进行申请,利用,释放,归还连接池等待下一次使用
-
加入连接池技术的JdbcUtils工具类的优化
1. 引入ThreadLocal 模拟线程场景,每一个线程都是用自己的连接对 象!ThreadLocal<T> ThreadLocal <Connecion> public ThreadLocal() public void set(T value) :将指定的值绑定在当前线程 上(绑定) public void remove() : 解绑,某个线程使用完毕之后,连 接对象归还连接池 之后,需要将连接对象从线程中解绑! public T get() : 获取当前线程中的执行的值 用法: 创建一个ThreadLocal<类型> Connection模拟线程场景! 在DuridJdbcUtils提供一个静态方法-----> getConnection()--->Connection 1)从当前线程中获取连接对象, 2)判断如果当前这个连接对象==null 3)需要从连接池中获取连接对象,绑定在当前线程上 4)返回从连接池中连接对象 提供一个静态方法-- coss--->关闭资源 当将Connection连接对象close之后,归还连接池中,当前线程 应该将绑定 连接对象,进行解绑!
加入连接池的工具类优化:
DruidJdbcUtils工具类代码实现
import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; /** * 加入Druid连接池的工具类 * * 1)现在目的就创建数据源--javax.sql.DataSource接口--->通过DruidDataSourceFactory创建数据源 * 2)模拟真实场景 */ public class DruidJdbcUtils { //声明数据源 private static DataSource ds = null ; //模拟线程 :每一个线程使用自己的Conneciton private static ThreadLocal<Connection> t1 = new ThreadLocal<>(); //构造方法私有化,外界类不能new private DruidJdbcUtils(){} //静态代码块 static{ try { //当前工具类一加载,读取src下面的druid.properties配置文件 InputStream inputStream = DruidJdbcUtils.class.getClassLoader(). getResourceAsStream("配置文件.properties"); //创建属性集合列表,将配置文件资源输入流加载属性集合列表中 Properties prop = new Properties() ; prop.load(inputStream) ; //通过DruidDataSourceFactory创建DataSource对象---- >DruidDataSource具体子实现类 给ds重写赋值 ds = DruidDataSourceFactory.createDataSource(prop); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 获取数据源,就是将配置文件的内容加载到了DataSource * @return */ public static DataSource getDataSource(){ return ds ; } /** * 从连接池中获取连接对象 ---->首先需要创建DataSource接口对象 * @return */ public static Connection getConnection(){ try { //1)从当前线程获取Conection Connection conn = t1.get(); if(conn==null){ //2)当前线程中没有连接对象 //需要从DataSource连接池获取连接对象 conn = ds.getConnection(); //3)将当前连接对象绑定在当前线程上 t1.set(conn); } return conn ; } catch (SQLException throwables) { throwables.printStackTrace(); } return null ; } //释放资源 针对DQL语句 public static void close(ResultSet rs, PreparedStatement ps ,Connection conn){ if(rs!=null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(ps!=null){ try { ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn!=null){ try { conn.close(); //归还连接池中 //需要从当前线程ThreadLocal进行解绑 t1.remove(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } /** * 针对DDL或者DML语句 * @param ps * @param conn */ public static void close( PreparedStatement ps ,Connection conn){ close(null,ps,conn); } //开启事务 ---控制事务:连接对象必须使用同一个! public static void setAutoCommit() throws SQLException { //从连接池获取连接对象 Connection conn = getConnection(); //手动提交 conn.setAutoCommit(false) ; } //回滚事务 public static void rollBackAndClose() throws SQLException { Connection conn = getConnection(); //回滚 conn.rollback(); conn.close(); //解绑 t1.remove(); } //提交事务 public static void commitAndClose() throws SQLException { Connection conn = getConnection(); //提交事务 conn.commit(); conn.close(); //解绑 t1.remove(); } }
二. 单元测试
-
步骤
1)导包junit的核心包以及依赖包 2)提供单元测试方法----->定义一个功能,没有返回值,没有参数,在方法上面@Test标记,它是一个单元测试方法 3)@Before,标记的方法是在单元测试方法之前先执行----->初始化的操作 4)@After,标记的方法是在用单元测试执行之后执行---->释放资源
-
单元测试代码示例
import org.junit.Before; import org.junit.Test; import java.sql.SQLException; import java.util.List; @SuppressWarnings("all") public class DbUtilsTest { private productDao pd; @Before public void init(){ pd = new ProductImpl(); } @Test public void testFindAll() throws SQLException { List<Product> list = pd.findAll(); for(Product product:list){ System.out.println(product); } } @Test public void testAdd() throws SQLException { pd.add(new Product(6,"热水袋",8,"温暖你的一切")); } @Test public void testDeleteById() throws SQLException { pd.deleteById(1); } @Test public void testUpdateById() throws SQLException { pd.updateByid(new Product(2,"华为手机",3000,"充电5分钟,通话两小时")); } @Test public void testFindByid() throws SQLException { Product pr = pd.findById(6); System.out.println(pr); } @Test public void testFindByName() throws SQLException { List<Product> list = pd.findByName("%华%"); for (Product product : list) { System.out.println(product); } } }
-
注意:单元测试中不能有键盘录入
四. dbUtils的使用
-
使用
1. 导入核心包 commons-dbutils-1.6/7.jar 2. 创建核心执行器:QueryRunner QueryRunner qr = new QueryRunner(DruidJdbcUtils.getDataSource()); 3. 准备SQL String sql = "select password from admin where username = ?"; 4. 执行SQL //查询 Admin admin = qr.query(sql, new BeanHandler<>(Admin.class),username); //更新 int count = qr.update(sql,name);
五. JDBC控制事务
通过sql指令的方式控制事务
1)开启事务:start transaction ;
2)执行多个sqL语句/或者多张表的sqL--添加/删除/修改
3)如果有问题,回滚事务rollback;
没有问题,提交事务最终 commit ;
JDBC方式控制事务
java.sql.Connection:
void setAutoCommit (boolean autoCommit) throws sQLException 设置提交模式
参数为false,表示禁用自动提交;为true,默认就是自动提交
void rollback() throws sQLException
事务回滚,撤销之前的所有更新操作; (前提必须处于手动提交
void commit() throws sQLException 提交事务, 将数据永久保存!
六. 完整demo
-
项目结构
-
实体类
public class Product { private int id; private String pname; private int pprice; private String pdesc; public Product() { } public Product(int id, String pname, int pprice, String pdesc) { this.id = id; this.pname = pname; this.pprice = pprice; this.pdesc = pdesc; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } public int getPprice() { return pprice; } public void setPprice(int pprice) { this.pprice = pprice; } public String getPdesc() { return pdesc; } public void setPdesc(String pdesc) { this.pdesc = pdesc; } @Override public String toString() { return "product{" + "id=" + id + ", pname='" + pname + '\'' + ", pprice=" + pprice + ", pdesc='" + pdesc + '\'' + '}'; } }
-
接口
import java.sql.SQLException; import java.util.List; public interface productDao { /** * 查询所有员工 * @return */ List<Product> findAll() throws SQLException; /** * 添加商品数据 */ void add(Product product) throws SQLException; /** * 通过商品id删除 */ void deleteById(int id) throws SQLException; /** * 更新商品信息 */ void updateByid(Product product) throws SQLException; /** * 通过ID查询 */ Product findById(int id) throws SQLException; /** * 模糊查询 */ List<Product> findByName(String name) throws SQLException; }
-
实现类
import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.junit.Before; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class ProductImpl implements productDao { static DataSource dataSource = DruidJdbcUtils.getDataSource(); static QueryRunner qr = new QueryRunner(dataSource); @Override public List<Product> findAll() throws SQLException { String sql = "select * from product"; List<Product> list = qr.query(sql, new BeanListHandler<>(Product.class)); return list; } @Override public void add(Product product) throws SQLException { String sql = "insert into product(id,pname,pprice,pdesc) values (?,?,?,?)"; //获取预编译执行对象 int count = qr.update(sql, product.getId(), product.getPname(), product.getPprice(), product.getPdesc()); System.out.println("插入成功,共影响行数为:"+count); } @Override public void deleteById(int id) throws SQLException { //准备SQL String sql = "delete from product where id = ?"; int count = qr.update(sql,id); System.out.println("删除成功,共影响行数为:"+count); } @Override public void updateByid(Product product) throws SQLException { //准备SQL String sql = "update product set pname = ?,pprice = ?,pdesc = ? where id = ?"; int count = qr.update(sql,product.getPname(),product.getPprice(),product.getPdesc(),product.getId()); System.out.println("更新成功,共影响行数为:"+count); } @Override public Product findById(int id) throws SQLException { //准备SQL String sql = "select * from product where id = ?"; Product product = qr.query(sql, new BeanHandler<>(Product.class),id); return product; } @Override public List<Product> findByName(String name) throws SQLException { //准备SQL String sql = "select * from product where pname like ?"; List<Product> list = qr.query(sql, new BeanListHandler<>(Product.class), name); return list; } }
-
测试类
import org.junit.Before; import org.junit.Test; import java.sql.SQLException; import java.util.List; @SuppressWarnings("all") public class DbUtilsTest { private productDao pd; @Before public void init(){ pd = new ProductImpl(); } @Test public void testFindAll() throws SQLException { List<Product> list = pd.findAll(); for(Product product:list){ System.out.println(product); } } @Test public void testAdd() throws SQLException { pd.add(new Product(6,"热水袋",8,"温暖你的一切")); } @Test public void testDeleteById() throws SQLException { pd.deleteById(1); } @Test public void testUpdateById() throws SQLException { pd.updateByid(new Product(2,"华为手机",3000,"充电5分钟,通话两小时")); } @Test public void testFindByid() throws SQLException { Product pr = pd.findById(6); System.out.println(pr); } @Test public void testFindByName() throws SQLException { List<Product> list = pd.findByName("%华%"); for (Product product : list) { System.out.println(product); } } }