PreparedSatement预编译对象解决SQL注入安全漏洞。
PreparedStatement对象继承了Statement对象。
用法步骤:
(1)得到连接。
(2)拼写sql语句使用占位符(?)代替参数值的位置。
(3)得到一个预编译对象PreparedStatement发送sql语句到数据库。
(4)注入参数
(5)通知数据库执行该sql语句
(6)分析结果。
小结一下API:
PreparadStatement对象的获取:
Connection下的方法:
-- PreparedStatement prepareStatement(String sql)
PreparadStatement对象的注入数据的方法:
-- void setString(int parameterIndex, String parameter)
-- void setInt(int parameterIndex, int parameter)
-- void setObject(int parameterIndex, Object parameter)
注入对应占位符位置:从1开始。
总结:
预编译对象的优点:1.SQL语句可以使用占位符。 2.安全机制更高(可以避免SQL注入)
3.性能好(先发送sql语句给数据库先预编译)
4.后期注入参数,代码十分优雅。
结论:请使用PreparedStatement对象而不是Statement。
*/
public class LoginDemo01 {
public static void main(String[] args) {
/** 1.先让用户输入用户名和密码 */
Scanner sc = new Scanner(System.in);
System.out.println("请输入登陆名:");
String loginName = sc.nextLine();
System.out.println("请输入密码:");
String passWord = sc.nextLine();
/** 2.校验登陆名称和密码 */
login(loginName , passWord);
}
public static void login(String loginName, String passWord) {
Connection con = null ;
PreparedStatement pstm = null;
ResultSet rs = null ;
try{
/** (1)得到连接。 */
con = ConnectionFactory.getConnection();
/** (2)拼写sql语句使用占位符(?)代替参数值的位置。 */
String sql = "select * from tb_user where loginName = ? and passWord= ?";
/** (3)得到一个预编译对象PreparedStatement发送sql语句到数据库先编译解析,但并未执行。*/
pstm = con.prepareStatement(sql);
/** (4)注入参数*/
// 这个1代表第一个占位符注入参数值:loginName
pstm.setString(1 , loginName);
// 这个2代表第二个占位符注入参数值:passWord(setString会自动进行SQL注入的排查一旦发现SQL注入就返回查询失败的结果!)
pstm.setString(2 , passWord);
/** (5)通知数据库执行该sql语句,并得到返回结果 ,这里才是真正执行已经发过去的sql语句 */
rs = pstm.executeQuery();
/** (6)分析结果。 */
if(rs.next()){
// 登录成功了
// 取出用户的真实用户名
String name = rs.getString("userName");
System.out.println(name+"登录系统成功了");
}else{
System.out.println("用户名或者密码错误了!");
}
}catch (Exception e){
e.printStackTrace();
}finally {
ConnectionFactory.close(con , pstm , rs);
}
}
}
###目标:使用PreparedStatement进行数据的修改操作
小结:
PreparedStatement是Statement的子类所以继承了以下方法:
-- int executeUpdate()
-- ResultSet executeQuery();
多了各种注入占位符参数值的方法:
-- void setString(int parameterIndex, String parameter)
-- void setInt(int parameterIndex, int parameter)
-- void setObject(int parameterIndex, Object parameter)
注入对应占位符位置:从1开始。
public class PreparedStatementDemo03 {
public static void main(String[] args) {
Connection con = null ;
PreparedStatement pstm = null ;
try {
// 1.先获取与数据库的连接
con = ConnectionFactory.getConnection();
// 2.拼装sql语句
String sql = "update tb_user set userName = concat(userName,?) where id = ?";
// 3.获取预编译对象,并同时发送sql语句到数据库预编译。
pstm = con.prepareStatement(sql);
// 4.开始给占位符注入参数值。
// 5.通知数据库正式执行
// 把id为2的数据中的userName改成原来的userName+_heima
pstm.setString(1 , "_heima");
pstm.setInt(2 , 2);
int count1 = pstm.executeUpdate();
System.out.println("修改了"+count1+"条数据!");
// 把id为4的数据中的userName改成原来的userName+_heima
pstm.setString(1 , "_heima");
pstm.setInt(2 , 4);
int count2 = pstm.executeUpdate();
System.out.println("修改了"+count2+"条数据!");
// 把id为6的数据中的userName改成原来的userName+_heima
pstm.setString(1 , "_heima");
pstm.setInt(2 , 6);
int count3 = pstm.executeUpdate();
System.out.println("修改了"+count3+"条数据!");
} catch (Exception e) {
e.printStackTrace();
}finally {
// 6.释放资源
ConnectionFactory.close(con , pstm , null);
}
}
}
连接池的概念:
是提供了若干个已经做好的连接对象,可供重复使用。
连接池的作用:
1)提高创建连接的速度,快速的得到一个连接对象
2)提高连接对象的使用率
3)提高系统的性能,可以尽力避免服务器奔溃。(只是一种方案)
连接池的类API(连接池==数据源):
javax.sql.DataSource 数据源接口,又称为连接池,没有实现,
由第三方厂商来实现。我们只需要学会使用连接池即可。
JDBC全部是规范,实现交给第三方公司。
所以使用连接池必须要有3个:
1)JDBC规范接口:javax.sql.DataSource
2)连接池的实现商。
3)数据库驱动。
常用连接池的介绍:
1)阿里巴巴-德鲁伊druid连接池:Druid是阿里巴巴开源平台上的一个项目(国产的)
2)DBCP(DataBase Connection Pool)数据库连接池,
是Apache上的一个Java连接池项目,
也是Tomcat使用的连接池组件。
3)C3P0是一个开源的JDBC连接池,目前使用它的开源项目有Hibernate,Spring等。
C3P0有自动回收空闲连接功能。(经典的世界级技术)
常用连接池参数:
1.初始连接数 (initPoolSize) 一开始连接池创建的时候创建多少个连接对象在连接池中
2.最大连接数(maxPoolSize) 连接池中最多有多少个连接对象
3.最长等待时间(maxWait) 如果连接池中没有空闲连接对象,当前用户等待多久以后抛出异常,单位是毫秒。
4.最长空闲回收时间 如果一个连接对象在连接池中长时间没有人使用,
多久以后被服务器回收。有些连接池并不支持这个功能。默认是0,0表示不回收。
所以使用连接池必须要有3个:
1)JDBC连接池的规范接口:javax.sql.DataSource
2)连接池的实现商。
3)数据库驱动。
使用步骤:
(1)去C3P0官网下载C3p0框架包。(都是一些jar包)
(2)导入连接池的Jar包到项目中去。
(3)导入数据库的驱动(已经做了):连接数据库底层还是必须要依赖数据库驱动
(4)找到C3p0的配置文件,放到src下面去。(强制性要求)
(5)文件名默认应该使用:c3p0-config.xml
(6)开始写源代码 实现 C3P0连接池
C3P0构造器:
C3P0的连接池叫 ComboPooledDataSource实现了java.sql.DataSource的规范。
-- public ComboPooledDataSource():
会自动读取src下的配置文件:c3p0-config.xml文件,
来得到一个连接池对象,使用默认的配置default-config
-- public ComboPooledDataSource(命名配置):
会自动读取src下的配置文件:c3p0-config.xml文件,
使用命名的配置named-config,来做连接池!
DataSource下获取连接的方法:
-- public Connection getConnection():获取连接
小结:
导入连接池的jar包,和驱动的jar包。
在src下配置c3p0的配置文件:c3p0-config.xml
创建连接池默认读取配置文件
获取连接对象。
*/
public class C3P0Demo02 {
public static void main(String[] args) {
try {
// 1.读取src下的默认配置文件c3p0-config.xml创建一个连接池!!
// 使用默认的default-config做连接池
DataSource dataSource = new ComboPooledDataSource();
//读取src下的默认配置文件c3p0-config.xml创建一个连接池!!
// 使用里面指定的otherc3p0做连接池
// DataSource dataSource = new ComboPooledDataSource("otherc3p0");
// 2.得到9个连接:保存5个,最多可用10个
for(int i = 0 ; i < 9 ; i++ ){
Connection con = dataSource.getConnection();
System.out.println(con);
// con.close();
}
// 3.拿第十个连接
Connection con = dataSource.getConnection();
System.out.println(con);
// 4.使用连接池的连接保存一个数据看是否可行!!
PreparedStatement pstm = con.prepareStatement("insert into tb_user(loginName , userName) value(?,?)");
pstm.setString(1,"tsgz");
pstm.setString(2,"铁扇美女");
System.out.println(pstm.executeUpdate()+"->保存成功!");
// con.close();
// 5.拿第11个连接
Connection con1 = dataSource.getConnection();
System.out.println(con1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
目标:Druid连接池,阿里巴巴实现的连接池使用。
使用步骤:
(1)去官网下载Druid框架包。(都是一些jar包)
druid-1.0.9.jar : -- 框架包,真正需要用的。
druid-1.0.9-javadoc.jar: -- 开发文档。
druid-1.0.9-sources.jar: -- 框架源代码。
(2)导入连接池的Jar包到项目中去。
(3)导入数据库的驱动(已经做了):连接数据库还是必须要依赖数据库驱动
(4)找到Druid的配置文件,放到src下面去。(强制性要求)
(5)开始写源代码
DruidDataSourceFactory.createDataSource(pro):创建连接池!
-- public static DataSource createDataSource(Properties properties)
参数:读取配置文件的对象。
小结:
把属性文件加载成属性集对象,然后转成连接池对象。
Druid自认为:是为监控而生的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池。
*/
public class DruidDemo01 {
public static void main(String[] args) {
try {
// 0.把druid.properties数据源配置文件转换成一个属性集对象Properties
Properties pro = new Properties();
// 加载属性文件的数据到对象pro中。
pro.load(new FileInputStream("src/druid.properties"));
// 1.创建阿里巴巴的连接池对象druid
// DruidDataSourceFactory:阿里巴巴提供的创建数据源的工厂!
// 通过属性集中的信息。把pro创建成连接池对象。
DataSource dataSource = DruidDataSourceFactory.createDataSource(pro);
// 2.得到9个连接:保存5个,最多可用10个
for(int i = 0 ; i < 9 ; i++ ){
Connection con = dataSource.getConnection();
System.out.println(con);
// con.close();
}
// 3.拿第十个连接
Connection con = dataSource.getConnection();
System.out.println(con);
// 4.使用连接池的连接保存一个数据看是否可行!!
PreparedStatement pstm = con.prepareStatement("insert into tb_user(loginName , userName) value(?,?)");
pstm.setString(1,"nfr");
pstm.setString(2,"牛夫人");
System.out.println(pstm.executeUpdate()+"->保存成功!");
// con.close();
// 5.拿第11个连接
Connection con1 = dataSource.getConnection();
System.out.println(con1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
德鲁伊连接工厂
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
/**
目标:使用druid连接池做一个连接工厂。
连接工厂:可以简化代码,我们以后可以直接拿连接。
连接池:提高操作数据库的性能。不需要每次都创建新连接。
小结:
一个连接工厂通常只加载一个连接池足以!!
*/
public class DruidConnectionFactory {
public static final String DRUID_FILE_NAME = "src/druid.properties";
// 定义连接池对象,必须只有一个。必须定义成静态变量!!
public static DataSource dataSource;
// 类加载就只初始化一个连接池
static{
try{
// 0.把druid.properties数据源配置文件转换成一个属性集对象Properties
Properties pro = new Properties();
// 加载属性文件的数据到对象pro中。
pro.load(new FileInputStream(DRUID_FILE_NAME));
// 1.创建阿里巴巴的连接池对象druid
// DruidDataSourceFactory:阿里巴巴提供的创建数据源的工厂!
// 通过属性集中的信息。把pro创建成连接池对象。
dataSource = DruidDataSourceFactory.createDataSource(pro);
}catch (Exception e){
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception {
// 2.从连接池中找连接返回!!
Connection con = dataSource.getConnection();
// 3.返回连接给调用者
return con;
}
/**
* 3.关闭资源
* @param con 连接对象
* @param stm 发送SQL语句的对象
* @param rs 结果集对象
*/
public static void close(Connection con , Statement stm , ResultSet rs) {
try{
if(stm!=null)stm.close();
}catch (Exception e){
e.printStackTrace();
}
try{
if(con!=null)con.close();
}catch (Exception e){
e.printStackTrace();
}
try{
if(rs!=null)rs.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
import java.sql.Connection;
public class Test {
public static void main(String[] args) throws Exception {
for(int i = 0 ; i < 10 ; i++){
Connection con = DruidConnectionFactory.getConnection();
System.out.println(con);
}
// 第11个连接
Connection con = DruidConnectionFactory.getConnection();
System.out.println(con);
}
}
目标:JdbcTemplate模板的使用。
JDBC已经能够满足大部分用户最基本的需求,但是在使用JDBC时,必须自己来写代码实现所有的功能。
如:获取PreparedStatement,设置SQL语句参数,关闭连接等步骤。
JdbcTemplate就是Spring对JDBC的封装,目的是使JDBC更加易于使用。
JdbcTemplate是Spring家族技术的一部分。使用JdbcTemplate编程我们只需要做以下步骤:
1)提供SQL语句,有占位符的
2)提供占位符的真实的值
JdbcTemplate使用步骤:
1.导入框架。
2.先做数据源(已经有了阿里巴巴的德鲁伊连接池,DruidConnectionFactory.java中已经有了)
3.拿JdbcTemplate对象
-- public JdbcTemplate(DataSource dataSource)
根据数据源对象来创建JdbcTemplate对象,然后就可以方便执行SQL语句
-- public void execute(final String sql)
执行代码:建表的操作.获取其他操作都可以
4.写Sql执行执行。
小结:
a.有一个数据源,交给JdbcTemplate创建自己的对象
b.拿着这个对象操作数据库即可!
注意:数据源可以是C3P0做的数据源,也可以是阿里巴巴的德鲁伊数据源,还可以是其他任意的数据源。
*/
public class JDBCTemplate01 {
public static void main(String[] args) {
// 1.把数据源包装成一个高级的JDBCTemplate对象。
// 参数就是数据源(连接池)。
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// 2.直接操作数据库了。
template.execute("delete from tb_user where id = 11");
}
}
import com._06factory.DruidConnectionFactory;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.Date;
/**
目标:JdbcTemplate模板的使用。DML操作。
JdbcTemplate:
1.导入框架。
2.先做数据源。(C3P0,支持Druid,一切数据源都支持)
3.拿JdbcTemplate对象
-- public JdbcTemplate(DataSource dataSource)
创建JdbcTemplate对象,方便执行SQL语句
-- public void execute(final String sql):DDL操作
执行代码:建表的操作:
-- int update(String var1, Object... var2) :DML操作。
参数一sql语句,参数二占位符参数值
4.写Sql执行执行。
*/
public class JDBCTemplate02 {
@Test
public void insertData(){
// 1.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// 2.操作数据库
String sql = "insert into tb_user(loginname, userName , passWord , create_date) " +
"values (? ,? ,? ,?)";
int count = template.update(sql,"qtds","齐天大圣","110",new Date());
System.out.println("保存了"+count+"条数据!");
}
@Test
public void deleteData(){
// 1.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// 2.操作数据库
String sql = "delete from tb_user where id in(? , ? )";
int count = template.update(sql,8 , 9);
System.out.println("删除了"+count+"条数据!");
}
@Test
public void updateData(){
// 1.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// 2.操作数据库
String sql = "update tb_user set userName= ? where id = ? ";
int count = template.update(sql,"红孩儿" , 10);
System.out.println("修改了"+count+"条数据!");
}
}
import com.06factory.DruidConnectionFactory;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
/**
目标:Template的其他功能。DQL查询
JdbcTemplate提供的丰富的查询API
1.查询的结果直接是返回一个整数int。 已过时
2.查询结果是一个long。queryForLong 已过时
3.查询结果是一个数据。 queryForObject 建议使用。
4.查询结果是一个Map集合
5.查询结果是一个List集合对象
6.查询的结果是一个自定义的List集合对象 :List<User>
7.BeanPropertyRowMapper返回自定义对象:第6点方法的优化。
*/
public class JDBCTemplate03 {
@Test
public void getMaxId(){
/** 1.查询的结果直接是返回一个整数int。 */
// a.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// b.写sql查询即可!
// 以下两个api已经过时,禁止使用!
// int rs = template.queryForInt("select max(id) from tb_user" );
// long rs = template.queryForLong("select max(id) from tb_user" );
/**
* 参数一:查询语句。
* 参数二:返回的结果类型。
*/
// c.public <T> T queryForObject(String sql, Class<T> requiredType)
Integer rs = template.queryForObject("select max(id) from tb_user" , Integer.class);
System.out.println(rs);
}
@Test
public void getMaxIDName(){
// 查询出id值最大的那个人的名字
// a.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// b.查询出id值最大的那个人的名字
String name = template.queryForObject("select userName from tb_user " +
"where id =(select max(id) from tb_user)" , String.class);
System.out.println(name);
}
/**
* 4.查询结果是一个Map集合
*/
@Test
public void getDataToMap(){
// 查询出id值最大的那个人的名字
// a.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// b.把数据查询成一个Map集合:查询id是13的数据
Map<String, Object> maps = template.queryForMap("select * from tb_user where id = ?" , 13);
System.out.println(maps);
}
/**
* 5.查询结果是一个List集合对象
*/
@Test
public void getListMaps(){
// 查询出id值最大的那个人的名字
// a.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// b.查询出来的结果是一个List集合的对象:每个元素是一个Map集合
// 一个Map集合代表一行数据!
List<Map<String, Object>> datas = template.queryForList("select * from tb_user where id < ? " , 7);
System.out.println(datas);
}
/**
* 6.查询的结果是一个自定义的List集合对象 :List<User>
*/
@Test
public void getUsers(){
// 查询出id值最大的那个人的名字
// a.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// b.把表中的全部用户都查询出来。
/***
* 参数一:查询的sql语句
* 参数二:你来解析结果集的对象RowMapper(行映射对象 行-new User())
*/
List<User> users = template.query("select * from tb_user", new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int i) throws SQLException {
System.out.println("正在转:"+i);
// 行映射成User对象的。
// rs会自动的一行一行的读取数据,程序员要把每行数据转换成对象数据返回!!
// 最终每行转换成的对象会自动加入到集合中去
// i:代表当前在转换第几行
User user = new User();
user.setId(rs.getInt("id"));
user.setCreateDate(rs.getString("create_date"));
user.setLoginName(rs.getString("loginName"));
user.setUserName(rs.getString("userName"));
user.setPassWord(rs.getString("passWord"));
return user;
}
});
System.out.println(users);
}
/**
* 7.BeanPropertyRowMapper返回自定义对象:第6点方法的优化。
*/
@Test
public void getUsers1(){
// 查询出id值最大的那个人的名字
// a.创建JdbcTemplate对象。基于数据源
JdbcTemplate template = new JdbcTemplate(DruidConnectionFactory.dataSource);
// b.把用户表查询成一个List<User>集合对象
/**
* 参数一:查询的sql语句
* 参数二:是结果集的处理,可以把结果集的数据自动注入到List<User>对象中去
* 但是要求是表的列名称必须与User类对象的属性字段名称一模一样,
* 否则自动注入失败!
*
* 数据库的下划线字段与Java的驼峰模式可以实现兼容和自动识别!!
* create_date = createDate (JdbcTemplate支持!!!)
* */
List<User> users = template.query("select * from tb_user" , new BeanPropertyRowMapper<>(User.class));
System.out.println(users);
}
}
public class User {
private int id ;
private String loginName ;
private String userName ;
private String passWord ;
private String createDate;
public User() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public String getCreateDate() {
return createDate;
}
public void setCreateDate(String createDate) {
this.createDate = createDate;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", loginName='" + loginName + '\'' +
", userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", createDate='" + createDate + '\'' +
'}'+"\n";
}
}