一、连接数据库
在昨天的基础上,现在我想来实现前后端以及数据库的连接。
CSDNhttps://mp.csdn.net/mp_blog/creation/editor/135388510?spm=1001.2101.3001.4503在cn2包下创建2个类,一个登录界面login,还有一个实现登录过程的loginServlet。
login:
public class login {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
boolean flag = loginServlet.selectByUsAndPa(username, password);
if (flag) {
System.out.println("登录成功");
} else {
System.out.println("用户名或密码错误");
}
}
}
这个登录界面很简单,Scanner接收用户名密码,通过loginServlet中的静态方法selectByUsAndPa判断是否登录成功。
loginServlet:
public class loginServlet {
public static boolean selectByUsAndPa(String username,String password) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection
("jdbc:mysql:///mydb_21?user=root&password=********");
stat = conn.createStatement();
rs = stat.executeQuery("SELECT * FROM user2 WHERE username='"
+ username + "' AND PASSWORD='" + password + "'");
if (rs.next()) return true;
else return false;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
conn = null;
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
stat = null;
}
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs = null;
}
}
}
}
}
}
}
先建立一个新的数据库user2:
CREATE TABLE user2 (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
PASSWORD VARCHAR(50)
);
整体格式与昨天所讲的基础别无二至,在查询的时候SQL语句用的是 SELECT * FROM user2 WHERE username = 'zs' AND PASSWORD = '123';
rs = stat.executeQuery("SELECT * FROM user2 WHERE username='"
+ username + "' AND PASSWORD='" + password + "'");
注意书写格式。
if (rs.next()) return true;
else return false;
这段语句有个坑要注意!判断是否查询到,不能写rs != null 因为rs本质上是个地址,不管查没查到地址都不会是空而是个具体的地址值,所以不管输入什么最终都会得到查询成功的结果。因此这里要用游标,如果有数据就说明查到了该位置是要查询的结果。
二、连接池
在写代码的时候我意识到一个问题,我多次重复了一段相同的操作-----创建连接,关闭连接:
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
conn.close();
stat.close();
rs.close();
频繁地这样操作,开关连接,会造成内存的不足。
类似List的扩容操作,如果容量一满就开辟一个新的空间,这样会造成系统资源的浪费,我们可以初始时开辟一定大小的空间,每次要扩容时再开辟一定大小,避免频繁扩容。
这里也是一样,为了避免频繁开关连接,初始时建立一个连接池,给其中分配一定数量的连接,当连接池中连接都用完再扩充连接池,避免多次扩充造成浪费。
首先建立内存池,存放链接。用List集合存放,并初始化5个链接放入连接池。
public class MyPool implements DataSource {
//存放链接
private static List<Connection> list = new ArrayList<>();
static {
try {
Class.forName("com.mysql.jdbc.Driver");
//初始化5个连接
for (int i = 0; i < 5; i++) {
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1194006164qwer");
Connection conn = DriverManager.getConnection("jdbc:mysql:///",info);
list.add(conn);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
因为MyPool类继承的是DataSource这个父类,因此需要在MyPool中实现父类的所有方法。
DataSource 是自 JDK 1.4 提供的一个标准接口,用于获取访问物理数据库的 Connection 对象。
但是并不是DataSource中所有的继承方法都需要实现,实现需要的几个就可以了:
主要实现这2个方法:getConnection() , returnConncetion
public Connection getConnection() throws SQLException {
if(list.size() <= 0){
for (int i = 0; i < 3; i++) {
Connection conn = DriverManager.getConnection
("jdbc:mysql:///mydb_21?user=root&password=********");
list.add(conn);
}
}
return list.remove(0);
}
public void returnConnection(Connection conn) {
if(conn != null) list.add(conn);
}
当连接池中没有链接时,也就是list.size() <= 0 ,往连接池中放入3个链接。
如果连接池中有链接,直接return返回,使外部拿到这个链接,注意返回类型,是Connection类型。要用remove方法,而不能用get方法。因为get(0)是一直返回第0个链接,而remove(0)会返回后将该位置的链接删除,后一个链接移动到该位置,如此便可return所有链接。
连接池的创建和获取链接、归还链接写完后,我们写个测试类来测试一下连接池的功能:
public class test01 {
public static MyPool myPool = new MyPool();
public void test01(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = myPool.getConnection();
ps.executeQuery("select* from user2 where id < ?");
ps.setString(1,"3");
rs = ps.executeQuery();
while (rs.next()){
String name = rs.getString("username");
System.out.println(name);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
myPool.returnConnection(conn);
}
}
}
1、创建MyPool对象,执行getConnection方法获取连接池中的链接。
2、用SQL查询语句将查询结果放入rs对象中,通过游标得到打印出来。
这里用的是PreparedStatement类型的对象,有别于Statement:
Statement每次执行sql语句,数据库都要执行sql语句的编译,最好用于仅执行一次查询并返回结果的情形,效率高于PreparedStatement。但存在sql注入风险
。
PreparedStatement是预编译执行的。在执行可变参数的一条SQL时,PreparedStatement要比Statement的效率高,因为DBMS预编译一条SQL当然会比多次编译一条SQL的效率高。安全性更好,有效防止SQL注入的问题。对于多次重复执行的语句,使用PreparedStatement效率会更高一点。执行SQL语句是可以带参数的,并支持批量执行SQL。
总的来说,Statement是适合一条语句查询,存在SQL注入风险;PreparedStatement适合可变参数的SQL语句和多次重复执行的SQL语句,能有效防止SQL注入问题。
在实际项目中我们不需要自己实现连接池,可以采用现成的连接池 (如cp30连接池)。
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Test02 {
//c3p0连接池
private static ComboPooledDataSource source=new ComboPooledDataSource();
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn=source.getConnection();
ps=conn.prepareStatement("select * from user2 where id<?");
ps.setInt(1,3);
rs=ps.executeQuery();
while (rs.next()){
String username = rs.getString("username");
System.out.println(username);
}
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
if(conn!=null){
source.close();
}
}
}
}