JDBC的使用
JDBC
一、JDBC的基础了解
1.JDBC是什么?JDBC是干什么的,用在什么地方?
JDBC是Sun公司制定的,JDBC为java语言连接数据库的通用接口JDBC。方便了java开发人员,开发人员不需要考虑特定的数据库的DBMS。JDBC不直接依赖于DBMS,而是通过(不同)驱动程序将(不同)sql语句转发给DBMS,由DBMS进行解析并执行,处理结果返回。
Mybatis、Mybatis Plus、Spring JPA等他们是对JDBC的封装,底层使用的其实还是JDBC。
2.JDBC的使用流程?
Statement:Sql执行接口,用于执行静态SQL语句。每次执行都会解析、编译和执行SQL语句,效率较低,但灵活性高。常用方法如下:
execute(String sql):通常用于DDL
executeUpdate(String sql):通常用于DML
executeQuery(String sql):用于DQL
步骤 | 阶段 | 主要API |
---|---|---|
第一步: | 注册驱动类 | Class.forName("com.mysql.cj.jdbc.Driver") |
第二步: | 建立连接 | DriverManager.getConnection(...) |
第三步: | 获取Sql执行对象 | Statement /PreparedStatement |
第四步: | 处理结果集 | Result |
第五步: | 关闭连接 | conn.close() |
//注册驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); //准备配置数据 String url = "jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"; String user = "root"; String password = "123456"; //建立连接 java.sql.Connection connection = DriverManager.getConnection(url, user, password); //获取语句对象 java.sql.Statement statement = connection.createStatement(); //准备sql调用语句对象方法执行sql String sql = "select * from emp"; ResultSet resultSet = statement.executeQuery(sql); //便利结果集 while (resultSet.next()) {String name = resultSet.getString("ename");System.out.println(" " + name); }
PreparedStatement:Statement的子接口,用于执行预编译的SQL语句。预编译的SQL语句只需要解析、编译一次,之后可以多次执行,提高了执行效率。适用于需要多次执行相同或类似SQL语句的场景。常用方法如下:(用于解决SQL注入风险)
execute() ;------用于DDL和DML
executeUpdate();-----用于DML
executeQuery();-----用于DQL
//注册驱动类 Class.forName("com.mysql.cj.jdbc.Driver"); //准备配置数据 String url = "jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"; String user = "root"; String password = "123456"; //建立连接 java.sql.Connection connection = DriverManager.getConnection(url, user, password); //获取语句对象,准备sql传给语句对象进行预编译 PreparedStatement preparedStatement = connection.prepareStatement("select * from dept where deptno = ?"); //将第一个问号处填入对应的值 preparedStatement.setInt(1, deptno); //调用语句对象方法执行sql ResultSet resultSet = preparedStatement.executeQuery(); //便利结果集 if (resultSet.next()){int id = resultSet.getInt("deptno");String dname = resultSet.getString("dname");String loc = resultSet.getString("loc");return new Dept(id, dname, loc); }else {return null; }
3.JDBC的工具封装
第一步:编写配置文件jdbc.properties
#低版本的mysql的驱动和url #driver=com.mysql.jdbc.Driver #url=jdbc:mysql://localhost:3306/bd1906?useUnicode=true&characterEncoding=utf8 #高版本的mysql的驱动和url driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai&useTimezone=true username=root passwd=mmforu
第二步:定义工具类DBUtil,读取配置文件,定义连接方法,关闭方法等
package util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; /*** 封装了JDBC连接数据库和关闭数据库的代码*/ public class DBUtil {private static String driver;private static String url;private static String username ;private static String password ; static{try {//读取配置文件InputStream is = DBUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");//创建一个Properties对象Properties properties = new Properties();//调用load方法,将流里的数据封装到Properties对象中properties.load(is);//取出数据给静态属性赋值driver = properties.getProperty("jdbc.driver");url = properties.getProperty("jdbc.url");username = properties.getProperty("jdbc.username");password = properties.getProperty("jdbc.password");} catch (Exception e) {e.printStackTrace();}}/*** 获取数据库连接对象* @return*/public static Connection getConnection() {Connection conn = null;try {Class.forName(driver);conn = DriverManager.getConnection(url, username, password);} catch (Exception e) {e.printStackTrace();}return conn;}/*** 关闭数据库连接* @param conn*/public static void close(Connection conn) {if (conn != null){try {conn.close();} catch (SQLException e) {e.printStackTrace();}}} public static void main(String[] args) throws SQLException {Connection connection = DBUtil.getConnection();System.out.println(connection);connection.close();} }
二、SQL的注入风险
1.SQL的注入风险是什么?
SQL的注入风险Statement对象会将sql语句的静态字符串直接传给数据库,当在where语句后拼接 or 1= 1时 会被数据库解析通过并执行,最终导致数据泄露
2.SQL的注入风险是怎么解决的?
SQL的注入风险的解决是通过预编译实现的,jdbc的prepareStatement先用SQL占位,编译通过后在给占位符进行赋值,转变成完整sql。
三、连接池技术
1.连接池是什么,连接池是干什么的?
与连接池建立连接过程,会消耗cpu内存资源及内存资源,从而数据库的性能大打折扣,如果一个网站处于高峰期,会有大量数据与数据库连接,断开,导致cpu资源耗尽。而连接池技术是提前创建数据库连接对象,并规定有多少个数据库连接对象,从而解决问题。
2.连接池原理是什么?
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
3.dbcp、c3p0、druid区别?
DBCP | C3P0 | Druid | |
---|---|---|---|
所属组织 | Apache Commons | 独立开源 | 阿里巴巴开源 |
性能 | 中等 | 较低(尤其在高并发下) | 最高(优化算法+并发控制) |
监控支持 | 基础JMX监控 | 有限监控 | 完整监控体系(Web页面+SQL分析) |
功能扩展 | 基础连接池 | 基础连接池 | 全栈式(防火墙/加密/统计等) |
维护状态 | 维护较少(稳定但更新慢) | 基本停止维护 | 活跃维护(持续更新) |
连接泄漏检测 | 需手动配置 | 有限支持 | 内置智能泄漏检测 |
适用场景 | 小型应用/Tomcat内置 | 传统老项目 | 高并发互联网应用/微服务架构 |
4.连接池的使用
1)dbcp
配置文件
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&useTimezone=true&useSSL=false&allowPublicKeyRetrieval=true username=root pwd=123456 initialSize=5 maxTotal=50 maxIdle=10 minIdle=3 maxWaitMillis=60000
工具类
public class DBPCUtil {private static String driver;private static String url;private static String username;private static String password;private static int maxTotal;private static int maxIdle;private static int minIdle;private static int initialSize;private static long maxWaitMillis; //声明一个dbcp连接池变量private static BasicDataSource pool;static{try{pool = new BasicDataSource();//连接池对象 //使用类加载器中提供的方法来获取字节流对象,同时指定配置文件InputStream is = DBPCUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");Properties prop = new Properties();prop.load(is);//将配置文件里的内容封装到prop对象内 driver = prop.getProperty("driver");url = prop.getProperty("url");username = prop.getProperty("username");password = prop.getProperty("pwd");maxTotal= Integer.parseInt(prop.getProperty("maxTotal"));maxIdle= Integer.parseInt(prop.getProperty("maxIdle"));minIdle= Integer.parseInt(prop.getProperty("minIdle"));initialSize= Integer.parseInt(prop.getProperty("initialSize"));maxWaitMillis= Long.parseLong(prop.getProperty("maxWaitMillis")); pool.setDriverClassName(driver);pool.setUrl(url);pool.setUsername(username);pool.setPassword(password);//连接池支持的最大连接数pool.setMaxTotal(maxTotal);//连接池支持的最大空闲数pool.setMaxIdle(maxIdle);//支持的最小空闲数pool.setMinIdle(minIdle);//连接池对象创建时初始化的连接数pool.setInitialSize(initialSize);//空闲等待时间pool.setMaxWaitMillis(maxWaitMillis);//注册驱动Class.forName(driver); }catch (Exception e){e.printStackTrace();}}public static Connection getConnection() throws SQLException {//从连接池中获取空闲对象return pool.getConnection();}public static void closeConnection(Connection conn, Statement stat, ResultSet rs){try {if(rs!=null){rs.close();}if(stat!=null){stat.close();}if(conn !=null){conn.close(); //会将连接对象归还给连接池内}} catch (SQLException e) {e.printStackTrace();}} public static void main(String[] args) throws SQLException {Connection conn = getConnection();System.out.println(conn);conn.close();} }
2)C3p0
配置文件
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config><default-config><property name="user">root</property><property name="password">123456</property><property name="jdbcUrl">jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&useTimezone=true&useSSL=false&allowPublicKeyRetrieval=true</property><property name="driverClass">com.mysql.jdbc.Driver</property><!-- 一次性增加的连接个数 --><property name="acquireIncrement">10</property><property name="maxPoolSize">50</property><property name="minPoolSize">2</property><property name="initialPoolSize">5</property><property name="maxIdleTime">600</property></default-config> </c3p0-config
工具类
public class DBUtilC3p0 {//构造器会自动检索src下有没有指定文件名称的配置文件,然后会自动赋值给其相应的属性private static ComboPooledDataSource pool = new ComboPooledDataSource("c3p0-config"); public static Connection getConnection() throws SQLException {//从连接池中获取空闲对象return pool.getConnection();}public static void closeConnection(Connection conn, Statement stat, ResultSet rs){try {if(rs!=null){rs.close();}if(stat!=null){stat.close();}if(conn !=null){conn.close(); //会将连接对象归还给连接池内}} catch (SQLException e) {e.printStackTrace();}} public static void main(String[] args) throws SQLException {Connection conn = getConnection();System.out.println(conn);conn.close(); }
3)druid
配置文件
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/db1 username=root password=123456 maxActive=20 minIdle=3 initialSize=5 maxWait=60000
工具类
public class DBUtil_druid {//创建连接池对象private static DataSource pool = null;static {try {//使用类加载器提供的方法读取db.properties,返回一个字节流对象InputStream is = DBUtil_druid.class.getClassLoader().getResourceAsStream("druid.properties");//创建Properties对象,用于加载流内部的数据Properties prop = new Properties();prop.load(is); //加载流内部的信息,以key-value的形式进行加载//调用静态方法,会自动给自己的属性赋值pool = DruidDataSourceFactory.createDataSource(prop); } catch (Exception e) {System.out.println("注册驱动失败");e.printStackTrace();}}/*** 获取连接对象** @return 连接对象* @throws SQLException* @throws ClassNotFoundException*/public static Connection getConnection() throws SQLException, ClassNotFoundException { //return DriverManager.getConnection(url, username, password);//从连接池中获取连接对象return pool.getConnection();} /*** 关闭数据库连接** @param rs 结果集对象* @param stat 处理sql的执行对象Statement* @param conn 连接对象*/public static void closeConnection(ResultSet rs, Statement stat, Connection conn) {try {if (rs != null) {rs.close();}if (stat != null) {stat.close();}if (conn != null) {conn.close();//释放连接,归还给连接池}} catch (Exception e) {System.out.println("数据库连接关闭失败");e.printStackTrace();}} public static void main(String[] args) throws ClassNotFoundException, SQLException {Connection conn = getConnection();System.out.println(conn);closeConnection(null, null, conn);} }
四、DAO设计模式
开发后端项目时,几乎所有软件公司的开发步骤大致都是一样的。步骤如下:
(1)创建项目-> (2)导入静态资源,如图片,音频、jar包等->(3)编写配置文件->(4)维护项目组织结构--> (5)开发持久层-->(6)开发业务层-->(7)开发控制层-->(8)开发视图层--> (9)测试。
1.DAO设计模式好处是
DAO这种设计模式,其核心思想是通过一个抽象层来隐藏具体的数据库访问技术,实现数据的持久化操作,并提供统一的接口给业务层调用。这种模式的优点在于,当底层数据存储技术或数据库结构发生变化时,无需更改业务逻辑层的代码,提高了系统的可维护性和灵活性。
2.DAO设计模式的代码编写主要分如下几个类
1.创建实体类
将类和表映射。从而完成传收据和接受数据。
public class User{private int id;private String name;private int age;//... }
2.接口
访问数据库的所有行为(增删改查),我们定义在一个接口中,具体的访问细节隐藏在实现类中。
优点是:提供一个统一的持久层访问入口
public interface UserDao {User getUserById(int id);List<User> getAllUsers();void insertUser(User user);void updateUser(User user);void deleteUser(int id); }
3.接口实现类
接口里的增删改查等抽象方法,我们需要提供一个实现类,来实现对数据库访问的具体细节。
public class UserDaoImpl implements UserDao{//...方法实现 }
4.工厂类型
定义一个工厂类型,来获取接口的实例,再次隐藏细节。
public class DaoFactory{private static UserDao userDao;private DaoFactory(){}public static synchronized UserDao getUserDaoInstance(){if(instance == null){//userDao = new UserDaoImpl();userDao = new UserDaoHibernateImpl();}return userDao;} }