jdbc 编程

Posted 甘劭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jdbc 编程相关的知识,希望对你有一定的参考价值。

一. JDBC是什么及 工作原理

 

持久化概念 :把数据保存到可掉电的设备里面存储;

 

JDBC: Java Database Connectivity   Java访问数据库的解决方案。

    1. 使用java代码发送sql语句的技术,JDBC定义了一套标准接口,即访问数据库的通用API,不同的数据库厂商根据各自数据库的特点去实现这些接口

    2. 组成JDBC的2个包为java.sql和javax.sql。开发JDBC应用需要这2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。

二. JDBC常用API

 1. JDBC定义了一些接口

  • 驱动管理 DriverManager
  • 连接接口 Connection   DatabasemetaData
  • 语句对象接口 Statement    PreparedStatement    CallableStatement
  • 结果集接口 ResultSet    ResultMetaData

  2. JDBC程序中的DriverManager用于加载驱动,并创建与数据库的连接,这个类的常用方法有:

DriverManager.registerDriver(new Driver());  
DriverManager.getConnection(URL, user, password); 

  注意:在实际开发中不推荐采用registerDriver方法注册驱动,原因有二:
        a. 查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象;
        b. 程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
        推荐方式:Class.forName("com.mysql.jdbc.Driver");
        采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的Connection对象。

   

 

   3.JDBC程序中的Connection对象用于代表数据库的连接,客户端与数据库所有交互都是通过Connection对象完成的.

createStatement();    //创建向数据库发送sql的statement对象  
prepareStatement(sql); //创建向数据库发送预编译sql的prepareStatement对象。这个更常用  
prepareCall(sql); //创建执行存储过程中的callableStatement对象  
setAutoCommit(boolean autoCommit); //设置事物是否自动提交 ,在JDBC中,事务是默认提交的true.  必须先设置事务为手动提交false 
commit(); //在链接上提交事物  
rollback(); //在此链接上回滚事物 

  

  4. JDBC程序中的Statement对象用于向数据库发送sql语句,Statement对象常用方法有:

executeQuery(String sql);   //用于向数据库发送查询语句  
executeUpdate(String sql);  //用于向数据库发送insert,update或delete语句  
execute(String sql);  //用于向数据库发送任意sql语句  
addBatch(String sql);  //把多条sql语句放到一个批处理中  
executeBath();  //向数据库发送一批sql语句执行  

  

  5. JDBC程序中的ResultSet对象用于代表sql语句的执行结果。

  ResultSet封装执行结果时,采用的类似于表格的方式。ResultSet对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用该对象的next()方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。

  由于ResultSet用于封装执行结果,所以该对象提供的都是用于获取数据的get方法:

//获取任意类型的数据  
getObject(int index);  //index表示列号  
getObject(String columnName);  //columnName表示列名,建议用这种方法,更好维护  
//获取指定类型的数据(int,String,Date等) getString(int index); getString(String columnName);

  ResultSet除了提供get方法以外,还提供了对结果集进行滚动的方法:

next();   //移动到下一行  
previous();  //移动到前一行  
absolute(int row);  //移动到指定行  
beforeFirst();  //移动到resultSet的最前面  
afterLast(); //移动到resultSet的最后面  

  

三. JDBC访问数据库的工作过程:  贾琏欲执事

  1. 加载驱动,建立连接
  2. 创建语句对象
  3. 执行SQL语句
  4. 处理结果集
  5. 关闭连接
//        1.注册驱动的两种方法:
//         方法一:
//          Driver driver = new com.mysql.jdbc.Driver(); //创建一个mysql驱动
//          DriverManger.registerDriver(driver);           //注册mysql驱动
            
//         方法二:使用反射注册mysql驱动
        Class.forName("com.mysql.jdbc.Driver");
        
        
//        2.通过DriverManger 驱动管理建立连接
        Connection connection = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/mysql001?useUnicode=true&characterEncoding=UTF-8","root", "root"); // 3.获得语句对象(需要SQL语句) String sql = "create table tb_user (`id` int(10) primary key auto_increment,`name` varchar(20),`age` int(10) );"; // 4.connction.createStatement() 创建一个Statement对象 将SQL语句发送到数据库; Statement stm= connection.createStatement(); stm.executeUpdate(sql); // 5.释放资源 stm.close(); connection.close();

 

  

四.代码重构. (把获取数据库连接和释放连接封装在方法里)

  1. 参数是硬编码的代码中,不推荐使用,不利于代码维护。

/*1. 参数:	
	private static String driverName = "com.mysql.jdbc.Driver";
	private static String url = "jdbc:mysql://localhost:3306/mysql001";
	private static String userName = "root";
	private static String passWorld = "root";
	
//2.  懒汉模式获取 JDBCUtil类的一个实例	
	private static JDBCUtil instance = null;
	private JDBCUtil(){}
	public static JDBCUtil getInstance(){
		if(instance == null){
			instance = new JDBCUtil();
		}
		return instance;
	}
//3. 加载驱动	
	static{
		try {
			Class.forName(driverName);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
//4. 获得链接对象
	public Connection getConn(){
		Connection conn = null;
		try {
			return DriverManager.getConnection(url,userName,passWorld);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	}
//5. 关闭链接资源
	public void close(ResultSet rs, Statement st, Connection conn){
		try {
			if(rs!=null) rs.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			try {
				if(st!=null) st.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(conn!=null) conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
*/

  

 

  2.把参数放到资源文件中 jdbc.properties

driverName = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mysql001
userName = root
password = root

  

public class JDBCUtil {
	
	static Properties prop = new Properties();
// 懒汉模式获取 JDBCUtil类的一个实例
	private JDBCUtil(){}
	private static JDBCUtil instance = null;
	public static JDBCUtil getInstance(){
		if(instance == null){
			instance = new JDBCUtil();
		}
		return instance;
	}
	
	static{
		try {
			prop.load(new FileInputStream("jdbc.properties"));
			Class.forName(prop.getProperty("driverName"));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
//获得链接对象
	public Connection getConnection(){
		Connection conn = null;
		try {
			conn = DriverManager.getConnection(prop.getProperty("url"),prop.getProperty("userName"),prop.getProperty("password"));
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return  conn;
	}
	
//关闭所有链接资源	
	public void closeAll(ResultSet rs, Statement st, Connection conn){
		try {
			if(rs!=null) rs.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			try {
				if(st!=null) st.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}finally {
				try {
					if(conn!=null) conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

  

五.domain 和 DAO 层认识

 

分层的目的:解耦,方便维护,责任分离;

1. 先创建一个domain包 ,在包里面创建相应的实体类 (Student

2. 在domain包的同一级,建一个dao包,这个包里面写接口:  ( IStudentDao

3. 在dao这个包里面再建一个包: impl 包 ,实现dao包里面的接口。(StudentDaoImpl)

 

 

下面的代码,没有使用 prepareStatement ,所以不正规,建议别看。

1. com.domain 包 User 实体类

package com.domain;

public class User {
	private String u_name;
	private int u_age;
	public String getU_name() {
		return u_name;
	}
	public void setU_name(String u_name) {
		this.u_name = u_name;
	}
	public int getU_age() {
		return u_age;
	}
	public void setU_age(int u_age) {
		this.u_age = u_age;
	}
	
	public User() {
	}
	public User(String u_name, int u_age) {
		super();
		this.u_name = u_name;
		this.u_age = u_age;
	}
	@Override
	public String toString() {
		return "User [u_name=" + u_name + ", u_age=" + u_age + "]";
	}
}

  

 

2. com.dao  包下的 IUserDao 接口 

package com.dao;

import java.util.List;
import com.domain.User;

public interface IUserDao {
	void addUserInfo(User user);
	void deleteUserInfo(User user);
	void updateUserAgeInfo(User user,int age);
	User selectUserInfoByName(String name);
	List<User> selectAllUserInfo();
}

  

3. com.dao.impl  包 UserDaoImpl 类

 

package com.dao.impl;

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import com.Util.JDBCUtil;
import com.dao.IUserDao;
import com.domain.User;

public class UserDaoImpl implements IUserDao{
	
	ResultSet rs = null;
	Statement st = null;
	Connection conn = null;
	User user = null;
	
/*常见的异常错误:No operations allowed after connection closed.
	因为 当 数据库的连接Connection是一个Static的,程序共享这一个Connection。
	那么第一次对数据库操作没问题,当把Connection关闭后,第二次还想操作数据库时Connection肯定不存在了。
所以为了解决这个问题:我们最好把  创建Connection的实例写到每个操作的方法中。
*/
	
/* 1. 增加用户信息*/ 
	@Test
	public void addUserInfo(User user){
			try {
				conn = JDBCUtil.getInstance().getConnection();
				st = conn.createStatement();
				int row = st.executeUpdate("insert into tb_user (id,`name`,age)values(null,\'"+user.getU_name()+"\',"+user.getU_age()+")");
				if(row > 0){
					System.out.println("添加用户成功");
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
			JDBCUtil.getInstance().closeAll(rs,st,conn);
	}
	
/*2. 删除用户信息*/
	@Test
	public void deleteUserInfo(User user) {
		try{
			conn = JDBCUtil.getInstance().getConnection();
			st = conn.createStatement();
			int row = st.executeUpdate("delete from tb_user where name = \'"+user.getU_name()+"\'");
			if(row > 0){
				System.out.println("删除用户成功");
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
		JDBCUtil.getInstance().closeAll(rs,st,conn);
	}
	
	
/*3. 修改用户信息*/
	@Test
	public void updateUserAgeInfo(User user,int age) {
		try{
			conn = JDBCUtil.getInstance().getConnection();
			st = conn.createStatement();
			int row = st.executeUpdate("update tb_user set age = "+age+" where name = \'"+user.getU_name()+"\'");
			if(row > 0){
				System.out.println("修改用户信息成功");
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
		JDBCUtil.getInstance().closeAll(rs,st,conn);
	}
	
	
/*4. 通过name查询一个用户信息*/
	@Test
	public User selectUserInfoByName(String name) {	
		try{
			//这里如果不写,当方法3中调用次方法时,就会因为 Connection 关闭而报错。
			conn = JDBCUtil.getInstance().getConnection();
			st = conn.createStatement();
			rs = st.executeQuery("select * from tb_user where `name` = \'"+name+"\'");
			while(rs.next()){
				user = new User();
				user.setU_name(rs.getString("name"));
				user.setU_age(rs.getInt("age"));
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
		JDBCUtil.getInstance().closeAll(rs,st,conn);
		return user;
	}
	
/*5. 查询所有用户信息*/
	@Override
	public List<User> selectAllUserInfo() {
		List<User> userlist = new ArrayList();
		try{
			conn = JDBCUtil.getInstance().getConnection();
			st = conn.createStatement();
			rs = st.executeQuery("select * from tb_user");
			while(rs.next()){
				User user = new User();
				user.setU_name(rs.getString("name"));
				user.setU_age(rs.getInt("age"));
				userlist.add(user);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
		JDBCUtil.getInstance().closeAll(rs,st,conn);
		return userlist;
	}
}

  

4. com.test 包  TestDao  测试类

package com.test;
import java.util.List;
import java.util.Scanner;
import org.junit.Test;

import com.dao.IUserDao;
import com.dao.impl.UserDaoImpl;
import com.domain.User;

public class TestDao {
	IUserDao userdao = new UserDaoImpl();
	
	@Test
	public void addUserInfo(){
		User user = new User("赵本本",75); //获取数据
		userdao.addUserInfo(user);  //将数据传到Dao层进行数据库操作
	}
	
	@Test
	public void deleteUserInfo(){
		User user = new User("赵本本",75);
		userdao.deleteUserInfo(user);;
	}
	
	/*修改用户信息*/
	@Test
	public void updateUserAgeInfo(){
		System.out.println("请输入要修改的用户姓名:");
		String name = new Scanner(System.in).nextLine();
		//将name传到dao层验证该用户是否存在
		User user = userdao.selectUserInfoByName(name);
		if(user != null){
			System.out.println("请输入修改后的用户年龄:");
			int age = new Scanner(System.in).nextInt();
			//将user对象和age传到dao层进行修改数据操作
			userdao.updateUserAgeInfo(user,age);;
		}else{
			System.out.println("不存在该用户,gameover!!");
		}
	}
	
	@Test
	public void selectUserInfoByName(){
		System.out.println("请输入查询的学生姓名");
		String name = "李四";
		System.out.println(userdao.selectUserInfoByName(name));
	}
	
	@Test
	public void selectAllUserInfo(){
		List<User> userlist = userdao.selectAllUserInfo();
		for (User user : userlist) {
			System.out.println(user);
		}
	}
}

  

6. 数据库表:

CREATE TABLE `tb_user` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `age` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

 

六 .Statement与PreparedStatement的区别

   1. 用PreparedStatement  可以写 动态参数化的查询(带参数的sql查询语句),不需要我们去拼接字符串,比起凌乱的字符串追加似的查询,PreparedStatement查询可读性更好、更安全。

   2. PrepareStatement是预处理语句    PreparedStatemnt的速度比Statement更快。

   3. PreparedStatement可以防止SQL注入式攻击

    1. 如果你是做Java web应用开发的,那么必须熟悉那声名狼藉的SQL注入式攻击。在SQL注入攻击里,恶意用户通过SQL元数据绑定输入,比如:某个网站的登录验证SQL查询代码为
      strSQL = "SELECT * FROM users WHERE name = \'" + userName + "\' and pw = \'"+ passWord +"\';

      恶意填入:

      userName = "1\' OR \'1\'=\'1";
      passWord = "1\' OR \'1\'=\'1";

      那么最终SQL语句变成了:

      strSQL = "SELECT * FROM users WHERE name = \'1\' OR \'1\'=\'1\' and pw = \'1\' OR \'1\'=\'1\';

      因为WHERE条件恒为真,这就相当于执行:

      strSQL = "SELECT * FROM users;

      因此可以达到无账号密码亦可登录网站。如果恶意用户要是更坏一点,用户填入 

    2. :strSQL = "SELECT * FROM users WHERE name = \'any_value\' and pw = \'\'; DROP TABLE users";
    3. 这样一来,虽然没有登录,但是数据表都被删除了。

 防SQL注入代码:

public Student login(String name, String password) {
		PreparedStatement ps = null;
		Connection conn = null;
		try {
			conn = JDBCUtil.getInstance().getConnection();
			String sql = "select * from student where username = ? and password = ?";
			ps = conn.prepareStatement(sql);
			ps.setObject(1, name);
			ps.setObject(2, password);
			rs = ps.executeQuery();
			
			while(rs.next()){
				stu = new Student();
				stu.setUserName(rs.getString("username"));
				stu.setPassWord(rs.getString("password"));
				stu.setAge(rs.getInt("age"));
				stu.setSex(rs.getBoolean("sex"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			JDBCUtil.getInstance().closeAll(rs,ps,conn);
		}
		return stu;
	}

 

@Test
		public void login1(){
			System.out.println("请输入用户名");
			String name = new Scanner(System.in).nextLine();
			System.out.println("请输入密码");
			String password = new Scanner(System.in).nextLine();
//  这种方式 如果密码和用户名只要其中一个  使用     \' or 1=1 or \'	都能登录成功。		
			Student stu = studao.login(name, password);
			if(stu!=null){
				System.out.println("登录成功");
			}else{
				System.out.println("登录失败");
			}
		}

 

 

7.登录的三种方式

 

.登录的正规方式: 先判断用户名,再判断密码。

@Override
	public Student login(String name) {
		try {
			conn = JDBCUtil.getInstance().getConnection();
			String sql = "select * from student where username = ?";
			PreparedStatement ps = conn.prepareStatement(sql);
			ps.setString(1, name);
			rs = ps.executeQuery();
			
			while(rs.next()){
				stu = new Student();
				stu.setUserName(rs.getString("username"));
				stu.setPassWord(rs.getString("password"));
				stu.setAge(rs.getInt("age"));
				stu.setSex(rs.getBoolean("sex"));
			}
		} catch (Exception e) {
			
		}finally {
			JDBCUtil.getInstance().closeAll(rs,st,conn);
		}
		return stu;
	}

  

  

 

 参考链接:https://www.cnblogs.com/shanheyongmu/p/5897176.html

     https://blog.csdn.net/github_39430101/article/details/77844465

以上是关于jdbc 编程的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——JS中的面向对象编程

VSCode自定义代码片段9——JS中的面向对象编程

面试常用的代码片段

mysql jdbc源码分析片段 和 Tomcat's JDBC Pool

使用 Pygments 检测代码片段的编程语言

面向面试编程代码片段之GC