JDBC


title: JDBC

JDBC

  • 密码的密文存储:在存储用户密码时,为了保证数据的安全性,一般采用密文存储。

此处我们采用的是MD5加密,并且创建了一个MD5Util帮助类

public class MD5Util {
	
	public static String getMd5(String s) {
		MessageDigest md5 = null;
		try {
			md5 = MessageDigest.getInstance("md5");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		byte bs[] = md5.digest(s.getBytes());
		StringBuffer sb2 = new StringBuffer();
		//将每个字节转换为2个16进制字符串。
		for(byte b:bs) {
			//字符串格式化函数,将一个对象格式化为字符串展示,x:16进制,2:长度为2 ,0不足两位用0充填
			sb2.append( String.format("%02x", b) );
		}
		return sb2.toString();
	}
	
}

JDK类库给我们提供了MessageDigest类来实现各种加密算法。

通过MessageDigest.getInstance(“md5”);来获取一个md5算法的加密类实例。

md5.digest(输入),根据输入获取加密后的的散列值,返回的散列值是一个长度为16的字节数组。

一般我们希望输入的是文本,加密后得到的也是问题,所以需要将16为的字节数组转换为固定长度的字符串。

根据二进制和字节的相关知识,一个字节是8个2进制位,而每4个2进制就可以转换为1个16进制。

所以我们可以将1个字节转换为2个16进制字符,而String.format函数就提供了该功能,将整数格式化为字符串。

execute函数与ResultSetMetaData

  • execute功能

之前我们使用过executeUpdate语句来执行修改,返回值为整数;

使用executeQuery语句来执行查询,返回值为结果集ResultSet;

这里我们介绍一下execute函数的使用,当我们不确定sql语句到底是什么类型的时候,例如是依赖用户输入的一个sql,

那么我们可以使用execute函数。

无论是查询还是修改,甚至是DLL语句,都可以通过execute函数执行。

  • execute返回值

该函数返回一个boolean类型数据,表示执行的sql语句是否有结果集。

例如,如果execute执行的是查询,返回值为true;如果执行的是修改,返回值为false。

当执行的是修改时,我们可以继续调用Statement的getUpdateCount函数来获取影响的行;

当执行的是查询时,我们可以继续调用Statement的getResultSet函数来获取结果集。

  • ResultSetMetaData

结果集元数据,用来描述结果集中的信息,如返回的结果有多少列,每列的名字,数据类型等。

但是我们不能直接获取结果集中有多少行数据。

通过结果集ResultSet的getMetaData函数可以得到元数据对象。

  • ResultSetMetaData使用场景

当我们的查询不确定,例如依赖用户输入时,那么我们就无法确定查询结果又多少列,

那么通过rs.getXXX(列编号)来获取某一行的数据时,我们就不知道列编号要写到几结束。

那么此时我们就可以通过ResultSetMetaData这个元数据对象来获取列编号有多少。

  • 综合案例
public static void main(String[] args) {
	
	Connection conn = null;
	PreparedStatement ps = null;
	Statement st = null;
	ResultSet rs = null;
	try {
		conn = JDBCUtil.getConn();
		st = conn.createStatement();
		String sql = "select * from student";//此处可以是用户输入的任何sql
		String sql2 = "update student set age=20";
		
		boolean b = st.execute(sql);//b表示sql的执行结果是否有结果集
		//如果execute执行的是查询,b为true;如果执行的是修改,b为false
		if(!b) {
			int rows = st.getUpdateCount();
			System.out.println("受影响的行数:"+rows);
		}else {
			 rs = st.getResultSet();
			 //结果集的元数据metadata,描述结果集中有多少列,每列的名字是什么等信息
			 ResultSetMetaData md =  rs.getMetaData();
			 int cc =  md.getColumnCount();//从元数据中获取列数目
			 
			 //打印列名
			 for(int i=1;i<=cc;i++) {
				 System.out.print( md.getColumnName(i) +"  ");
			 }
			 System.out.println("\n---------------------------");
			 
			 //打印查询到的内容
			 while(rs.next()) {
				 for(int i=1;i<=cc;i++) {
					 System.out.print(rs.getString(i)+"  ");
				 }
				 System.out.println();
			 }
		}
	}catch (SQLException e) {
		e.printStackTrace();
	}finally {
		JDBCUtil.closeAll(rs, st, conn);
	}
}

执行结果

img

jdbc制作基于控制台的sql交互模式


//基于控制台的SQl语句查询

public class OraToEcli {
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		Connection conn = null;
		ResultSet rs = null;
		PreparedStatement pstm = null;
		Statement st = null;
		
		try {
			conn = JDBCUtil.getConnection();
			//向数据库发送SQL语句
			st =  conn.createStatement();
			System.out.println("成功连接数据库!!!!");
			while(true) {
				System.out.print("SQL>");
				//控制台输入的SQl语句
				String sql = sc.nextLine();
				pstm = conn.prepareStatement(sql);
				//定义循环出口
				if(sql.equals("bye")) {
					System.out.println("数据库连接断开!");
					break;
				}
				
				//若SQl语句是DML返回false
				//若SQl语句是DQL返回true
				//boolean flag = st.execute(sql);
				boolean temp = pstm.execute();
				if(temp) {//DQL查询语句
					//rs = st.getResultSet();
                   //获得结果集元数据
					rs = pstm.getResultSet();
                   //调用返回结果的函数
					returnResult(rs);
				}else {//DML操作语句
                   //得到操作的行数
					int rows = pstm.getUpdateCount();
					System.out.printf("有%d行操作成功",rows);
					System.out.println(rows);
				}
			}
		} catch (SQLException e) {
			System.out.println(e);
		}finally {
			JDBCUtil.closeAll(rs, pstm, conn);
			if(st != null) {
				try {
					st.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					System.out.println(e);
				}
			}
		}
	}
	
	//返回结果集函数
	static void returnResult(ResultSet rs) {
		try {
			//结果集的元数据metadata,描述结果集中有多少列,每列的名字
			ResultSetMetaData md = rs.getMetaData();
			//获取元数据中列的数目cc
			int cc = md.getColumnCount();
			//打印列名
			for(int i = 1;i<=cc;i++) {
				System.out.print(md.getColumnName(i)+" / ");
			}
			System.out.println();
			System.out.println("----------------------------------");
			
			//打印查询的内容
			while(rs.next()) {
				//打印每一行中的每一列
				for(int i = 1;i<=cc;i++) {
					System.out.print(rs.getString(i)+" / ");
				}
				System.out.println();
			}
			
		} catch (SQLException e) {
			System.out.println(e);
		}
	}

}

文件读取时路径问题

  1. 文本文件一般整行读取并处理,所以过程需要:
  • 获取字节流InputStream
  • 将字节流包装为字符流InputStreamReader
  • 将字符流再包装为BufferReader,方便整行读取
  1. IO流读取时,文件路径的写法
  • Kt1.class.getResourceAsStream(“backup.txt”)

    这种获取输入流的方式很好用,表示获取Kt1类相同包下的backup.txt文件的输入流,采用这种基于类编译路径的方式避免了从物理系统查找文件。

  • Kt1.class.getClassLoader().getResourceAsStream(文件名)

    多了一个getClassLoader(),就表示从src根路径读取文件,而不是从Kt1当前包下。

数据库连接池

  1. 连接池作用
  • 在jdbc操作数据库的一系列步骤中,获取数据库连接是第一步也是最消耗时间的一步。当系统启动时,我们的每个数据库操作都要经历创建连接和释放连接的步骤,这对系统的处理时间有很多影响。
  • 解决问题的办法
    • 在系统启动时提前创建好一些连接并维护起来(可以想象为将连接资源放入的一个池子中),当需要操作数据库时,就从池子中获取连接;当使用完后,就把连接放回池子,以供其他操作使用。这个过程就避免了连接的不断创建和释放,提升了性能。
  1. 基础使用
  • 常用的数据库连接池

    • DBCP和C3p0
  • 代码

    public static void main(String[] args) throws Exception {
    	//加载配置文件
    	Properties pp = new Properties();
    	InputStream ins = Kt6.class.getResourceAsStream("jdbc.properties");
    	pp.load(ins);
    	//创建连接池
    	DataSource ds = BasicDataSourceFactory.createDataSource(pp);
    	//获取连接
    	Connection conn = ds.getConnection();
    	System.out.println(conn.getClass());
    	//并不是真正的关闭连接,而是将连接放回连接池
    	conn.close();
    }
    

    1 DBCP中数据库连接池对象为DataSource,也称为数据源,我们就可以通过该对象的getConnection方法来获取连接池的连接。

    2 数据源的创建依赖配置信息,需要通过Properties对象加载配置文件,第3~5行代码,然后将该对象作为参数传入createDataSource方法来创建数据源

    3 通过conn.close来关闭连接,在连接池中这并不真正的关闭连接,而是将连接资源归还连接池。

    4 在jdbc.properties文件中指定连接池创建的一些必要信息和配置参数,文件内容示例如下:

    //创建连接的必要信息
    driverClassName=oracle.jdbc.driver.OracleDriver
    url=jdbc:oracle:thin:@localhost:1521:orcl
    username=hr
    password=hr
    
    initialSize=10		//初始连接池的大小
    maxActive=20		//最大连接数
    maxIdle=8			//最大空闲连接数,多于这个数,连接被释放
    minIdle=2			//最小空闲,少于这个数,新建连接
    maxWait=60000		//最大等待时间
    
  1. 封装数据库连接池工具类(基于DBCP连接池帮助类)
public class DBCPUtil {
	private static DataSource ds;
	//连接池只需要自动创建一次即可
	static {
		Properties pp = new Properties();
		InputStream ins = DBCPUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
		try {
			pp.load(ins);
			ds = BasicDataSourceFactory.createDataSource(pp);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
    //获取连接
	public static Connection getConn() {
		
		try {
			return ds.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	//关闭连接
	public static void closeAll(ResultSet rs,Statement st,Connection conn) {
		try {
			if(rs!=null)
				rs.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if(st!=null)
				st.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		try {
			if(conn!=null)
				conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

核心编程思想

create table t_person(
	id  number(3) primary key,
	name varchar2(30) ,
	sex number(1),
	age number(2),
	mobile varchar2(11),
	address varchar2(300)
)
向表中添加数据
  1. 数据库客户端操作数据库
  2. 登陆数据库
  3. 打开发送SQL的窗口
  4. 编写SQL语句
  5. 执行SQL
  6. 若是DQL,需要接收结果集
  7. 释放资源,关闭数据库
使用JDBC操作数据库
  1. 加载驱动
  2. 根据驱动连接数据库,获取数据库连接对象
  3. 根据数据库连接对象获取加载器
  4. 发送执行SQL
  5. 若是SQL查询语句,需要ResultSet rs结果集
  6. 释放资源

ResultSet结果集

  • ResultSet:结果集,存放查询语句的查询结果(rs中存放的是select语句查询结果的字段名(别名),可能不是表中的原有字段名,调用get方法时,以别名为准)

image-20200907165353175

动态参数

方式1:SQL字符串拼接
  • 用法:将SQL需要的数据,通过字符串拼接的方式拼接到字符串SQL中
  • 缺点:
    • 操作繁琐,拼接麻烦
    • SQL注入问题:在拼接SQL字符串时,可能拼接上一下奇奇怪怪的东西,造成数据库不安全的问题(数据丢失,数据删除…)
  • 优点:可以拼接表名、字段名、关键字(desc asc)

image-20200908115709209

 public static boolean login(String username,String password) throws Exception {
   		Class.forName("oracle.jdbc.OracleDriver");
   		Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr");
   		String sql = "select * from t_account where username = '"+username+"' and password = '"+password+"'";
   		System.out.println(sql);
   		PreparedStatement pstm = conn.prepareStatement(sql);
   		ResultSet rs = pstm.executeQuery();
   		boolean flag = rs.next();
   		rs.close();
   		pstm.close();
   		conn.close();
   		return flag;
   	}
   请输入用户名:
   abcd
   请输入密码:
   a'or'a'='a
   select * from t_account where username = 'abcd' and password = 'a'or'a'='a'
方式2:pstm占位符
  • 用法:在编写字符串SQL时,允许使用?对数据进行一个占位
    • 特点
      • ?只能给数据占位,不能给关键字,表名,字段名等占位
      • 可以解决SQL注入问题
    • 缺点:不能给关键字,表名,字段名等占位

image-20200908120819808

  • 操作步骤

    • 先写使用占位符的SQL

      • String sql = "select * from t_account where username = ?";
        
    • 发送SQL

      •   PreparedStatement pstm = conn.prepareStatement(sql);
        
    • 绑定参数,将实际参数绑定到相应的占位符上

      •   //给?赋值  pstm.setXxx(第几个?,实际的数据)
          pstm.setString(1,username);
          pstm.setString(2, password);
        
    • 发送参数,并执行SQL

      •   pstm.executeQuery();
        
总结
  • 若做数据的动态处理,使用pstm占位符
  • 若做关键字、字段名等动态处理,使用字符串拼接

批处理

  • addBatch(),将参数保存在Java本地(JVM内存中)
  • execuBatch(),将本地参数一次发送给数据库
class TestBatch {
	public static void main(String[] args) throws Exception {
		long t1 = System.nanoTime();
		Class.forName("oracle.jdbc.OracleDriver");
		Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr");
		//创建使用占位符的SQL
		String sql = "insert into t_person values(person_seq.nextval,?,?,?,?,?)";
		//发送SQL,并获取执行器
		PreparedStatement pstm = conn.prepareStatement(sql);
		for (int i = 1; i <=100000; i++) {
			//给?绑定参数
			pstm.setString(1,"haha");
			pstm.setInt(2, 1);
			pstm.setInt(3, 18);
			pstm.setString(4, "123456");
			pstm.setString(5, "硅谷广场14楼");
			pstm.addBatch();//将参数保存在java本地
			if(i%500==0) {
				pstm.executeBatch();//将多组参数一次发送给数据库
			}
		}
		//将本地可能剩余的参数发送给数据库
		pstm.executeBatch();
		//System.out.println(count);
		//释放资源
		pstm.close();
		conn.close();
		long t2 = System.nanoTime();
		System.out.println((t2-t1)/1E9);
	}
}

JDBCUtil工具类

  • 作用:简化代码,减少代码冗余,提高代码可重用性

  • 实现:抽取重复的代码,封装到一个方法当中


public class JDBCUtil {
	private static Properties prop = new Properties();
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
	
	//静态代码块减少加载配置文件的次数
	static {
		//获取配置文件的输入流
		//获取类加载输入流,后面的文件夹路径是以src为根目录的
		try (InputStream is = JDBCUtil.class.getResourceAsStream("/jdbc.properties");){
			prop.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
        //加载驱动
        //根据key获取集合中对应的value
        Class.forName(prop.getProperty("driverClassName"));
	}
	
	//获取数据库连接
	public static Connection getConnection() {
		//获取线程空间中的数据
		Connection conn = tl.get();
		//对conn进行判断,若为null,则肯定是业务层在获取conn,新建conn,并将conn放入线程空间中
		//若conn不为null,则肯定是Dao层在获取conn,此时可以直接return conn;
		if(conn == null) {
			try {
				
				//获取数据库连接对象
				conn = DriverManager.getConnection(prop.getProperty("url"),prop.getProperty("user"),prop.getProperty("password"));
				tl.set(conn);
			} catch (Exception e) {
				throw new RuntimeException("数据库连接异常",e);
			}
		}
		//返回数据库连接对象
		return conn;
	}
	
	//释放数据库资源
	public static void closeAll(ResultSet rs,PreparedStatement pstm,Connection conn) {
		if(rs != null)
			try {
				rs.close();
			} catch (SQLException e) {
				throw new RuntimeException("释放ResultSet关闭异常",e);
			}
		if(pstm != null)
			try {
				pstm.close();
			} catch (SQLException e) {
				throw new RuntimeException("释放PreparedStatemend关闭异常",e);
			}
		if(conn != null)
			try {
				conn.close();
                tl.remove();
			} catch (SQLException e) {
				throw new RuntimeException("释放Connection关闭异常",e);
			}
	}
	
	public static void closeAll(PreparedStatement pstm,Connection conn) {
		closeAll(null,pstm,conn);
	}
}

项目实战设计模式

ORM
  • 概念:Object Relational Mapping 将数据库中表中的一行数据映射成一个java对象
  • 作用:符合java面向对象的编程思想,通过对象更加方便的管理数据
Dao
  • 概念:Data Access Object 数据访问对象
  • 作用:专门负责对数据库的增、删、改、查操作,里面的方法不是固定的,可以根据具体需求,自行修改
  • 注意:为了满足职能单一原则,Dao类中每一个方法,都应该只对应一种操作

JDBC三层结构

1.视图层

  • 作用:直接面向用户,负责与用户进行数据的交互(接收(Scanner)、展示(syso))

  • 注意:暂时用Test测试类代替

2. 业务层–service

  • 作用:程序给用户提供的服务(业务、功能);例如:注册、登陆、搜索、转账等等

  • 注意:每一种功能,都应该对应业务层中的一个方法

  • 业务层设计:

    • 业务层接口:XxxService 例如:PersonService
    • 业务层接口实现类:XxxServiceImpl 例如:PersonServiceImpl
  • 注意:大部分情况,一个功能(业务方法)中需要调用多个Dao层方法,为了保证业务正常实现,必须在业务层进行事务控制

  • Connection conn数据库连接对象

    conn.setAutoCommit(false); 开启事务

    conn.commit(); 提交事务

    conn.rollback(); 回滚事务

  • 注意:为了统一管理,无论是否需要,业务层方法统统都需要加事务控制!!!

3.Dao层–dao

  • 作用:唯一可以直接操作数据的模块!!每一个数据库操作(增删改查)都需要对应Dao层的一个方法
  • Dao层设计:
    • Dao层接口:XxxDao 例如:PersonDao
    • Dao层接口实现类:XxxDaoImpl 例如:PersonDaoImpl

存在问题:事务控制失败!!

失败原因:Dao层中的数据库连接conn与业务层中数据库连接conn,不是同一个!!在业务层中的conn上做事务控制,无法控制到Dao层的conn上的SQL操作

image-20200909172049139

解决办法:让Dao层和业务层使用同一个数据库连接对象conn

  • 方法1:将业务层中conn当做参数传递个Dao层中每个需要使用的方法

  • 方法2:业务层和Dao层都执行在主线程中!每个线程中都有一块儿独立的空间(线程空间TreadLocal).无论任何时候,都是业务层先拿到conn,当业务层需要conn时,创建一个新的conn,并放入线程空间中;当执行到Dao层代码,Dao层需要获取conn时,不再新建,而是从线程空间中获取,保证业务层和Dao层使用的是同一个conn;此时,Dao层中不能关闭conn,而应该在业务层中关闭!!!!!

image-20200909173056578

ThreadLocal
  • 概念:线程中一块儿独立的空间,可以用来存储一个变量(数据)

  • 语法:ThreadLocal t1 = new ThreadLocal();

  • 例如:ThreadLocal t1 = new ThreadLocal();

  • 方法:

    • set(T v); 往线程空间中存放数据
    • T get(); 从线程空间中获取数据
    • remove(); 删除线程空间中的数据
  • 用在JDBC工具类中

总结

1.常见API

  1. Connection
  2. prepareStatement(sql) 获取执行器
  3. setAutoCommit(false); 开启事务
  4. close(); 释放conn资源
  5. PreparedStatement
  6. executeupdate(); 执行DML
  7. executeQuery(); 执行DQL
  8. addBatch(); 将多组数据缓存在java本地
  9. executeBatch(); 将本地缓存数据一次性提交给数据库
  10. setXxx(); 绑定参数
  11. close(); 释放资源
  12. ResultSet
  13. next() 使游标下移一行;若指向一行数据,则返回true,否则返回false
  14. getXxx() 获取当前列对应的字段或序列号的值
  15. close();
  16. ThreadLocal 线程空间
  17. set(T v) 将数据存放到线程空间中
  18. T get() 获取线程空间中的数据
  19. remove() 移除线程空间中的数据

2.三层结构

  • view
  • service
  • dao

3.项目开发流程

  • 建表
  • 创建一个项目
  • 在项目根目录下创建lib文件夹(和src同级),用来存放jar包
  • Build Path
  • 在src下导入配置文件
  • 创建包结构:公司域名倒置.模块名
    1. com.baizhi.util 用来存放工具类
    2. com.baizhi.entity 实体类
    3. com.baizhi.dao dao接口
    4. com.baizhi.dao.impl dao接口实现类
    5. com.baizhi.service 业务层接口
    6. com.baizhi.service.impl 业务层接口实现类
    7. com.baizhi.test 测试类

4. 注意事项

  1. 所有的service方法都需要进行事务控制
  2. 在Dao层不要关闭conn
  3. 在service层一定要关闭conn
    ,则返回true,否则返回false
  1. getXxx() 获取当前列对应的字段或序列号的值
  2. close();
  3. ThreadLocal 线程空间
  4. set(T v) 将数据存放到线程空间中
  5. T get() 获取线程空间中的数据
  6. remove() 移除线程空间中的数据

2.三层结构

  • view
  • service
  • dao

3.项目开发流程

  • 建表
  • 创建一个项目
  • 在项目根目录下创建lib文件夹(和src同级),用来存放jar包
  • Build Path
  • 在src下导入配置文件
  • 创建包结构:公司域名倒置.模块名
    1. com.baizhi.util 用来存放工具类
    2. com.baizhi.entity 实体类
    3. com.baizhi.dao dao接口
    4. com.baizhi.dao.impl dao接口实现类
    5. com.baizhi.service 业务层接口
    6. com.baizhi.service.impl 业务层接口实现类
    7. com.baizhi.test 测试类

4. 注意事项

  1. 所有的service方法都需要进行事务控制
  2. 在Dao层不要关闭conn
  3. 在service层一定要关闭conn
  4. 对包名、类名、接口名的定义尽量规范
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值