数据库
JDBC
-
Java应用通过
JDBC API
调用驱动,操作对应的数据库
-
核心组件
-
使用步骤(六步)
- 导包:
import java.sql.*
- 初始化驱动
- 连接:
DriverManager.getConnection()
- 执行查询
- 提取数据
- 关闭连接
// 在project structure中创建Library引入jar包 public static void main(String[] args) { Connection conn = null; Statement stat = null; ResultSet result = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 数据库名称 falsk, 注意格式 // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); // Home End // 3. 定义SQL stat = conn.createStatement(); result = stat.executeQuery("select * from short_url;"); // int effect = stat.executeUpdate("insert into short_url(token, url) values('test2', 'http://porn.com')");// 插入,返回受影响行数 // 4. 取结果 while (result.next()) { System.out.println("id:"+result.getString("id")+ " token:"+result.getString("token")); } // 如果是增删改: // if (effect > 0) { // System.out.println("执行成功!"); // }else { // System.out.println("执行失败!"); // } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { // 5. 关闭连接 try { if (result != null) { result.close(); } if (stat != null) { stat.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } }
- 导包:
SQL注入
- 在执行的SQL语句中加入一些特殊字符,让引擎误识别,能绕开权限获取数据,造成数据的不安全
- 避免SQL注入的方法:创建预状态通道
- 在使用JDBC的第三步,通过
createStatement
创建的是状态通道,现在使用PreparedStatement
创建预通道public static void main(String[] args) { Connection conn = null; Statement stat = null; PreparedStatement pstmt = null; ResultSet result = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 数据库名称 falsk, 注意格式 // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); // Home End // 3. 定义SQL并执行 // stat = conn.createStatement(); String sql = "select * from short_url where id=?"; // 所有的参数使用 ? 占位 pstmt = conn.prepareStatement(sql); String id = "1"; // 替换占位符 pstmt.setString(1, id); // 使用字符串 result = pstmt.executeQuery(); // result = stat.executeQuery("select * from short_url;"); // 查询 // 4. 取结果 while (result.next()) { System.out.println("id:"+result.getString("id")+ " token:"+result.getString("token")); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { // 5. 关闭连接 try { if (result != null) { result.close(); } if (pstmt != null) { stat.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } }
- 预状态通道会先编译sql语句,再去执行,比statement执行效率高
- 预状态通道支持占位符?,给占位符赋值的时候,位置从1开始
- 预状态通道可以防止sql注入,原因:预状态通道在处理值的时候全部以字符串的方式处理
两表关系
- 数据库通过外键建立两表关系
- 实体类通过属性的方式建立两表关系,类似对象关系映射ORB
- 类名=表名
- 属性名=字段名
- 使用下面的数据表练习
CREATE TABLE `student` ( `stuid` int(11) NOT NULL AUTO_INCREMENT, `stuname` varchar(255) DEFAULT NULL, `teacherid` int(11) DEFAULT NULL, PRIMARY KEY (`stuid`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `student` VALUES ('1', 'aaa', '3'); INSERT INTO `student` VALUES ('2', 'bb', '1'); INSERT INTO `student` VALUES ('3', 'cc', '3'); INSERT INTO `student` VALUES ('4', 'dd', '1'); INSERT INTO `student` VALUES ('5', 'ee', '1'); INSERT INTO `student` VALUES ('6', 'ff', '2'); DROP TABLE IF EXISTS `teacher`; CREATE TABLE `teacher` ( `tid` int(11) NOT NULL AUTO_INCREMENT, `tname` varchar(255) DEFAULT NULL, PRIMARY KEY (`tid`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `teacher` VALUES ('1', '张三老师'); INSERT INTO `teacher` VALUES ('2', '李四老师'); INSERT INTO `teacher` VALUES ('3', '王五'); -- teacher表是主表,一个tid对应多个学生,所以后面的Java代码中要多加个集合属性
- 接下来创建对应的类和属性,显然,这些类都是bean
public class Student { /* 学生表是多端,或者说子表 */ private int stuid; private String stuname; // 外键列一般不生成方法 private int teacherid; public int getStuid() { return stuid; } public void setStuid(int stuid) { this.stuid = stuid; } public String getStuname() { return stuname; } public void setStuname(String stuname) { this.stuname = stuname; } } public class Teacher { /* 教师表是一端,受学生表外键的约束 */ private int tid; private String tname; private List<Student> list=new ArrayList<Student>(); // 新增属性,一对多查询,保存学生信息 public int getTid() { return tid; } public void setTid(int tid) { this.tid = tid; } public String getTname() { return tname; } public void setTname(String tname) { this.tname = tname; } public List<Student> getList() { return list; } public void setList(List<Student> list) { this.list = list; } }
- 创建dao层,定义一对多的查询接口和实现类(一个老师对应多个学生)
// TeacherDaoImpl.java public class TeacherDaoImpl implements TeacherDao { @Override public Teacher getById(int tid) { Connection conn = null; PreparedStatement pstmt = null; ResultSet result = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 数据库名称 falsk, 注意格式 // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); // Home End // 3. 定义SQL并执行 预通道 String sql = "select * from student s, teacher t where s.teacherid=t.tid and tid=?"; // 所有的参数使用 ? 占位 pstmt = conn.prepareStatement(sql); pstmt.setInt(1, tid); // 都要使用字符串,数据库引擎会解析 result = pstmt.executeQuery(); // 4. 取结果 Teacher teacher = new Teacher(); List<Student> students = new ArrayList<Student>(); while (result.next()) { // 取出各自信息,注意顺序 teacher.setTid(result.getInt("tid")); teacher.setTname(result.getString("tname")); Student student = new Student(); student.setStuid(result.getInt("stuid")); student.setStuname(result.getString("stuname")); students.add(student); } teacher.setList(students); return teacher; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { // 5. 关闭连接 try { if (result != null) { result.close(); } if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } return null; } }
// test/Teac.java public static void main(String[] args) { // 操作数据库都会定义一个dao层,这里的dao和sql包,类似MVC模型的M TeacherDao daot = new TeacherDaoImpl(); // Impl是具体实现,类似List和ArrayList Teacher teacher = daot.getById(1); System.out.println("老师姓名:"+teacher.getTname()); // 一查多 List<Student> students = teacher.getList(); for (Student student:students) { System.out.println("\tsname: "+student.getStuname()); } }
- 多对一查询(每个学生都带上老师信息),也是类似的,在Student类中增加属性保存老师信息呗
// 增加属性,多对一查询 private Teacher teacher; public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; }
- 一对多:一方中有一个多方的集合
- 多对一:多方中有一个一方的数据
- 一对一:用的比较少,就是写SQL的区别,其他一样,两个表都要新增保存对方数据的属性
- 多对多,例如学生和科目,用到如下表:
CREATE TABLE `middle` ( `middleid` int(11) NOT NULL AUTO_INCREMENT, `stuid` int(11) DEFAULT NULL, `subid` int(11) DEFAULT NULL, PRIMARY KEY (`middleid`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; INSERT INTO `middle` VALUES ('1', '1', '1'); INSERT INTO `middle` VALUES ('2', '1', '2'); INSERT INTO `middle` VALUES ('3', '1', '3'); INSERT INTO `middle` VALUES ('4', '1', '5'); INSERT INTO `middle` VALUES ('5', '2', '2'); INSERT INTO `middle` VALUES ('6', '3', '2'); INSERT INTO `middle` VALUES ('7', '4', '2'); INSERT INTO `middle` VALUES ('8', '5', '2'); INSERT INTO `middle` VALUES ('9', '6', '2'); DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `stuid` int(11) NOT NULL AUTO_INCREMENT, `stuname` varchar(255) DEFAULT NULL, `teacherid` int(11) DEFAULT NULL, PRIMARY KEY (`stuid`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `student` VALUES ('1', '张三', '3'); INSERT INTO `student` VALUES ('2', '李四', '1'); INSERT INTO `student` VALUES ('3', '王五', '3'); INSERT INTO `student` VALUES ('4', '赵六', '1'); INSERT INTO `student` VALUES ('5', '花花', '1'); INSERT INTO `student` VALUES ('6', '潇潇', '2'); DROP TABLE IF EXISTS `subject`; CREATE TABLE `subject` ( `subid` int(11) NOT NULL AUTO_INCREMENT, `subname` varchar(255) DEFAULT NULL, PRIMARY KEY (`subid`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `subject` VALUES ('1', 'java'); INSERT INTO `subject` VALUES ('2', 'ui'); INSERT INTO `subject` VALUES ('3', 'h5'); INSERT INTO `subject` VALUES ('4', 'c'); INSERT INTO `subject` VALUES ('5', 'c++'); INSERT INTO `subject` VALUES ('6', 'c#');
- 多对多要用到中间表
middle
;创建对应的类 - 在Student类和Subject类中增加相应的属性配置
// 配置多对多,增加属性 private List<Subject> subjects; public List<Subject> getSubjects() { return subjects; } public void setSubjects(List<Subject> subjects) { this.subjects = subjects; }
public class Subject { private int subid; private String subname; // 配置多对多,新增学生集合 private List<Student> students; public List<Student> getStudents() { return students; } public void setStudents(List<Student> students) { this.students = students; } public int getSubid() { return subid; } public void setSubid(int subid) { this.subid = subid; } public String getSubname() { return subname; } public void setSubname(String subname) { this.subname = subname; } }
- dao层定义接口和实现类,并测试
// 代码很长,这里就放两个SQL // select * from student s, subject sub, middle m where s.stuid=m.stuid and sub.subid=m.subid and s.stuid=? // select * from subject sub, student s, middle m where s.stuid=m.stuid and sub.subid=m.subid and s.subid=? // 剩下的就是JDBC五步骤,分开来看还是用中间表实现了,一对多查询
- 多对多要用到中间表
事务
- 事务特点:ACID(背)
- 一般设置手动提交
// 获取连接时 conn.setAutoCommit(false); // 不自动提交
- 可以设置保存点
savepoints
,回滚时可以指定到某个点,而不是全部失败public static void main(String[] args) { Connection conn = null; Statement stat = null; PreparedStatement pstmt = null; Savepoint sp1 = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); conn.setAutoCommit(false); // 不自动提交 // 3. 定义SQL并执行 String sql_a = "insert into middle(stuid, subid) values(6, 3)"; pstmt = conn.prepareStatement(sql_a); int result = pstmt.executeUpdate(); // 设置一个保存点------------------ sp1 = conn.setSavepoint("sp1"); // 手动失败----------------------- System.out.println(5/0); String sql_b = "insert into middle(stuid, subid) values(2, 3)"; pstmt = conn.prepareStatement(sql_b); int result2 = pstmt.executeUpdate(); // 手动提交事务 conn.commit(); // 4. 取结果 if (result > 0) { System.out.println("执行成功!"); } else { System.out.println("执行失败!"); } } catch (Exception e) { // 注意这里要用父类Exception,不然不走这块的的异常 e.printStackTrace(); try { // conn.rollback(); // 一次性全部回滚 conn.rollback(sp1); conn.commit(); // 这次提交的就是回滚点之前的那个插入操作 } catch (SQLException throwables) { throwables.printStackTrace(); } } finally { // 5. 关闭连接 try { if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } }
- 用到事务的地方很多,比如转账,一个减钱,一个加钱
批处理
- 批量更新数据库数据,我们在预通道方式下测试,相比通道就是多了一个传参的操作,不是直接传SQL到
addBatch
public static void main(String[] args) { Connection conn = null; PreparedStatement pstmt = null; Savepoint sp1 = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); conn.setAutoCommit(false); // 不自动提交 // 3. 定义SQL并执行 String sql_a = "insert into teacher(tname) values(?)"; pstmt = conn.prepareStatement(sql_a); // 批处理--------------- pstmt.setString(1, "Roy"); pstmt.addBatch(); pstmt.setString(1, "Allen"); pstmt.addBatch(); pstmt.setString(1, "Andy"); pstmt.addBatch(); int[] result = pstmt.executeBatch(); // 返回每条语句影响的行数 // 设置一个保存点 sp1 = conn.setSavepoint("sp1"); // 手动提交事务 conn.commit(); // 4. 取结果 for (int i:result) { System.out.println(i); } } catch (Exception e) { // 注意这里要用父类Exception,不然不走这块的的异常 e.printStackTrace(); try { conn.rollback(sp1); conn.commit(); // 这次提交的就是回滚点之前的那个插入操作 } catch (SQLException throwables) { throwables.printStackTrace(); } } finally { // 5. 关闭连接 try { if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } }
- 可以通过类反射执行上述所有的SQL操作,了解即可
工具类
- 为了避免每次写一堆重复步骤,封装一个工具类,包含连接库、增删改查的方法
import java.sql.*; import java.util.List; public class DBUtil { //1.定义需要的工具类对象 protected Connection connection=null; protected PreparedStatement pps=null; protected ResultSet rs=null; protected int k=0;//受影响的行数 private String url="jdbc:mysql://localhost:3306/flask"; private String username="root"; private String password="123456"; //2.加载驱动 static{ try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } //3.获得连接 protected Connection getConnection(){ try { connection=DriverManager.getConnection(url,username,password); } catch (SQLException e) { e.printStackTrace(); } return connection; } //4.创建通道 protected PreparedStatement getPps(String sql){ try { getConnection();//insert into users values(?,?,?,?,) pps=connection.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } return pps; } //5.给占位符赋值 list中保存的是给占位符所赋的值 private void setParams(List list){ try { if(list!=null&&list.size()>0){ for (int i=0;i<list.size();i++) { pps.setObject(i+1,list.get(i)); } } } catch (SQLException e) { e.printStackTrace(); } } //6.增删改调取的方法 protected int update(String sql,List params){ try { getPps(sql); setParams(params); k= pps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } return k; } //7.查询时调取 protected ResultSet query(String sql, List list){ try { getPps(sql); setParams(list); rs=pps.executeQuery(); return rs; } catch (SQLException e) { e.printStackTrace(); } return null; } // 8. 关闭资源 protected void closeall(){ try { if (rs != null) { rs.close(); } if (pps != null) { pps.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
- 在dao层新增接口,并实现
@Override public Student getByStuId(int id) { Student student = new Student(); String sql = "select * from student where stuid=?"; List list = new ArrayList(); list.add(id); // 预参数集合 ResultSet result = query(sql, list); try { if (result.next()) { student.setStuid(result.getInt("stuid")); student.setStuname(result.getString("stuname")); } return student; } catch (Exception e) { e.printStackTrace(); } finally { closeAll(); } return null; } // test.java public static void main(String[] args) { TeacherDao daot = new TeacherDaoImpl(); Student student = daot.getByStuId(1); // 单表查询 System.out.println(student.getStuname()); }
- 一般在开发过程中,将固定在工具类中的字符串,放在属性文件中
- 名称必须是
xxx.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/flask?serverTimezone=UTC user=root password=123456
// 修改DBUtil private static String url; private static String username; private static String password; private static String driverName; //2.加载驱动 static{ // 有两种方式 try { InputStream inputStream = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties"); Properties properties = new Properties(); properties.load(inputStream); driverName = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("user"); password = properties.getProperty("password"); Class.forName(driverName); // 第二种方式 ResourceBundle bundle = ResourceBundle.getBundle("db"); driverName = bundle.getString("driver"); url = bundle.getString("url"); username = bundle.getString("user"); password = bundle.getString("password"); Class.forName(driverName); } catch (ClassNotFoundException | IOException e) { e.printStackTrace(); } }
- 名称必须是
连接池
- 将数据库连接作为对象存储在内存中,请求数据库时,从连接池中取出一个已建立的空闲连接对象
- 自定义连接池
- 需要定义指定的属性和方法
- 属性: 集合,放置连接
- 方法: 获取连接方法、回收连接方法
public class Pool{ static LinkedList<Connection> list = new LinkedList<Connection>(); static{ for (int i = 0; i < 10; i++) { // 在池子里初始化十个连接 Connection connection = JDBCUtils.newInstance().getConnection(); list.add(connection); } } /** * 从连接池子中获取连接的方式 * @return */ public static Connection getConnection(){ if (list.isEmpty()) { //JDBCUtils类是自定义类,封装了连接数据库的信息代码 Connection connection = JDBCUtils.newInstance().getConnection(); list.addLast(connection); } Connection conn = list.removeFirst(); return conn; } /** * 返回到连接池子中 */ public static void addBack(Connection conn){ if (list.size() >= 10) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ list.addLast(conn); //10 } } /** * 获取连接池子中连接数量的方法 */ public static int getSize(){ return list.size(); } }
- Java也定义了接口(规范),让我们实现连接池,具体实现代码如下
DataSource
接口有一个弊端,没有提供回收链接的方法,先装饰一下
public class MyConnection implements Connection { //将被装饰者导入 private Connection conn; private LinkedList<Connection> list; public MyConnection(Connection conn, LinkedList<Connection> list) { super(); this.conn = conn; this.list = list; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return conn.unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return conn.isWrapperFor(iface); } @Override public Statement createStatement() throws SQLException { return conn.createStatement(); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } @Override public CallableStatement prepareCall(String sql) throws SQLException { return null; } @Override public String nativeSQL(String sql) throws SQLException { return null; } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { } @Override public boolean getAutoCommit() throws SQLException { return false; } @Override 基于规范实现的连接池 public void commit() throws SQLException { conn.commit(); } @Override public void rollback() throws SQLException { conn.rollback(); } @Override public void close() throws SQLException { list.addLast(conn); } ... }
// 实现 DataSource 连接池 public class DataSourcePool implements DataSource{ static LinkedList<Connection> list = new LinkedList<Connection>(); static{ for (int i = 0; i < 10; i++) { Connection connection = JDBCUtils.newInstance().getConnection(); list.add(connection); } } public static int getSize(){ return list.size(); } @Override public Connection getConnection() throws SQLException { Connection conn = list.removeFirst(); MyConnection conn1 = new MyConnection(conn, list); return conn1; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } }
- 也就是说,不需要我们写连接池的代码,用上面的某种就可以
- 试一下DBCP的使用,将commons-dbcp-1.4.jar和commons-pool-1.5.6.jar导入进Modules
// 重写DBUtils public class DBUtil { //1.定义需要的工具类对象 protected Connection connection=null; protected PreparedStatement pps=null; protected ResultSet rs=null; protected int k=0;//受影响的行数 private static String url; private static String username; private static String password; private static String driverName; private static BasicDataSource basicDataSource = new BasicDataSource(); //2.加载驱动 static{ ResourceBundle bundle = ResourceBundle.getBundle("db"); driverName = bundle.getString("driver"); url = bundle.getString("url"); username = bundle.getString("user"); password = bundle.getString("password"); basicDataSource.setUsername(username); basicDataSource.setPassword(password); basicDataSource.setUrl(url); basicDataSource.setDriverClassName(driverName); basicDataSource.setInitialSize(15); } //3.获得连接 protected Connection getConnection(){ try { connection=basicDataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } //4.创建通道 protected PreparedStatement getPps(String sql){ try { getConnection();//insert into users values(?,?,?,?,) pps=connection.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } return pps; } //5.给占位符赋值 list中保存的是给占位符所赋的值 private void setParams(List list){ try { if(list!=null&&list.size()>0){ for (int i=0;i<list.size();i++) { pps.setObject(i+1,list.get(i)); // 从1开始 } } } catch (SQLException e) { e.printStackTrace(); } } //6.增删改调取的方法 protected int update(String sql,List params){ try { getPps(sql); setParams(params); k= pps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } return k; } //7.查询时调取 protected ResultSet query(String sql, List list){ // 传入要执行的SQL和参数数组 (预通道) try { getPps(sql); setParams(list); rs=pps.executeQuery(); return rs; } catch (SQLException e) { e.printStackTrace(); } return null; } // 8. 关闭资源 protected void closeAll(){ try { if (rs != null) { rs.close(); } if (pps != null) { pps.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
- dbcp没有自动回收空闲连接的功能,c3p0有自动回收空闲连接功能
- 在src下创建
c3p0-config.xml
,配置文件,名称固定;然后导入c3p0-0.9.1.2.jar<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/flask?serverTimezone=UTC</property> <property name="user">root</property> <property name="password">123456</property> <!-- 等待连接的超时时间,默认为0,代表无限等待,单位是毫秒 --> <property name="checkoutTimeout">30000</property> <!-- 检查空闲连接 默认为0 代表不检查 --> <property name="idleConnectionTestPeriod">30</property> <!-- 初始化连接 --> <property name="initialPoolSize">10</property> <!-- 最大空闲时间,超过这个时间的连接将被丢弃,默认为0,代表永远不关闭 --> <property name="maxIdleTime">50</property> <!-- 最大连接数 --> <property name="maxPoolSize">100</property> <!-- 最小连接数 --> <property name="minPoolSize">10</property> <!-- preparedStatement的缓存大小 --> <property name="maxStatements">200</property> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3 --> <property name="AcquireIncrement">5</property> </default-config> </c3p0-config>
- 你没看错,db.properties 都不需要了
// 只需要改两行,不需要static静态代码块了 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); //3.获得连接 protected Connection getConnection(){ try { connection = comboPooledDataSource.getConnection(); // connection=basicDataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; }
- 目前,最流行的是阿里开发的德鲁伊,使用过程类似DBCP,需要db.properties
// druid private static DruidDataSource druidDataSource = new DruidDataSource();
Web
- 学习目标
Tomcat
- 常见的开发模式:BS和CS
- 浏览器——服务器:通过浏览器访问网站
- 客户端——服务器:有客户端,不会实时更新代码
- Tomcat是目前最流行的Java服务器
- 服务器是部署在操作系统之上的,提供代码运行环境的容器
- 下载与安装
- 使用apache-tomcat-8.5.34,免安装,官网
- 要熟悉解压后每个文件夹的作用
- 启动:双击bin/startup.bat,如果闪退,可能是8080端口占用;也可能需要修改此文件
netstat -ano|findstr "8080" # 找到PID tasklist|findstr "PID" # 或者在startup.首行加一句 SET JAVA_HOME=D:\Java
- 访问看是否启动:http://localhost:8080/
- 使用shutdown.bat可以关闭
- 在我们自己编写的项目目录`webapps``下
- 新建文件夹,相当于创建一个项目
- 新建HTML页面,测试访问
- IDEA整合Tomcat
- 点击启动,自动打开浏览器访问了:http://localhost:8080/demo_war_exploded/
- 可以在tomcat配置文件中修改端口号
- 路径太长,一般会在上图中的页面修改,设置Application context为
/
- 默认打成war包,名称就是Artifact的名称,也是项目名称
web工程: src: 存放java源代码 .java文件,下设不同package web: web资源 WEB-INF(受服务器保护,浏览器不能直接访问) lib-存放第三方jar包 web.xml 为整个web工程的配置部署描述文件,servlet listener filter session等配置信息 index.jsp 在浏览器中输入工程名时,默认访问的资源
- 增加管理员信息:conf/tomcat-users.xml,感觉没啥用
<role rolename="manager-gui"/> <user username="tomcat" password="tomcat" roles="manager-gui"/>