JavaEE开发-第三节 JDBC
JDBC简介
如何通过Java操作数据库?
- JDBC,全称是Java Database Connectivity
- 在Java中用来规范如何访问关系型数据库,由各大数据库厂商去实现它
- 属于JavaSE的一部分
- 简单来说就是一套接口规范,定义了一些访问数据库的接口标准
- 各大厂商需要实现这些接口标准
- JDBC是JDK的一部分,只要安装了JDK,就有JDBC
- 调用方式
- java开发人员,使用JDBC接口,调用各大厂商的数据库
下载MySQL的JDBC实现(jar,驱动包)
- 8.0版本的驱动包同时支持MySQL 5.7/8.0
- 下载地址
- https://dev.mysql.com/downloads/connector/j/
- 进入后点击Archive,选择版本号,Operating System选择Platmform Independent 然后点击
(mysql-connector-java-8.0.21.zip)
Download,即可 - 然后解压,拿到解压文件夹中的:
mysql-connector-java-8.0.21.jar
包
- IDEA如何手动导入第三方jar包(纯java项目):
- 新建一个纯java项目:File->new->module->选择java->点击next,命名->点击OK
-
在项目目录下新建一个与src同级的lib文件夹
//纯java项目,非javaEE项目 03_JDBC lib src 03_JDBC.iml
- 复制jar包到lib目录下
- 右击jar包->添加为库(add as labrary)->level:选择模块库(Module Library) ->点击确定(OK)
JDBC使用步骤
- 将Driver (驱动程序) 注册到DriverManager (驱动程序管理者)
- Driver指的是各大数据库厂商实现的驱动,在上面下载的jar包的com.mysql.jdbc文件夹下有个Driver类
- DriverManager指的是JDBC实现的驱动程序管理者
- 利用DriverManager创建Connection(数据库连接)
- 利用Connection创建Statement(语句)
- 利用Statement执行SQL语句
- 关闭资源(关闭Statement、Connection等)
JDBC细节
- MySQL的url格式是
jdbc:mysql://IP地址:端口号/数据库名
- 比如:
jdbc:mysql://localhost:3306/zh
-
驱动类
MySQL驱动包6.x以前 MySQL驱动包6.x开始 驱动类 com.mysql.jdbc.Driver com.mysql.cj.jdbc.Driver 时区 需要指定一个确定的时区 比如在url后加上参数serverTimezone=UTC
-
6.x以后代码举例:
Class.forName("com.mysql.cj.jdbc.Driver"); DriverManager.getConnection("jdbc:mysql://localhost:3306/zh_info?serverTimezone=UTC","root","root");
代码举例
public class Main {
public static void main(String[] args) throws Exception {
//1. 注册Driver到DriverManager
//DriverManager.registerDriver(new Driver());
//装载一个类,就是将这个Driver类装载进JVM去,通过查看Driver类的源代码,发现有个静态方法直接实现了上面的一句代码,因此当JVM第一次装载这个Driver类时就会调用。下面这么写等价于上面
//而且这么写的好处是不需要导入Driver类,即import com.mysql.jdbc.Driver;
Class.forName("com.mysql.jdbc.Driver");
//2. 利用DriverManager创建Connection(数据库连接)
//url指的是mysql的服务器地址,但是注意协议是 jdbc:mysql:
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/zh_info","root","root");
//3. 利用Connection创建Statement(语句)
Statement statement = connection.createStatement();
//4. 利用Statement执行SQL语句
//将id==1的学生年龄改为100
statement.execute("UPDATE student SET age = 100 WHERE id =1");
//5. 关闭资源(关闭Statement、Connection等)
statement.close();
connection.close();
}
}
- 为什么要使用
Class.forName("com.mysql.jdbc.Driver");
代替DriverManager.registerDriver(new Driver());
?Class.forName("com.mysql.jdbc.Driver");
的本质是装载一个类-
查看mysql的驱动源码com.mysql.jdbc.Driver类的实现如下
public class Driver extends NonRegisteringDriver implements java.sql.Driver { // Register ourselves with the DriverManager static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } /** * Construct a new driver and register it with DriverManager * @throws SQLExceptionif a database error occurs. */ public Driver() throws SQLException { // Required for Class.forName().newInstance() } }
- 会发现有个static块,当Driver类被加载的时候会自动调用static中的代码,因此可以替换
- 还有一个好处,这么一些文件中就不需要导入Driver类,即
import com.mysql.jdbc.Driver;
,那么即使有换掉数据库,只需要把字符串换掉即可
JDBC版本
-
从JDBC 4.0开始, 显式注册驱动程序是可选的
//显示注册 DriverManager.registerDriver(new Driver()); Class.forName("com.mysql.jdbc.Driver");
- 我们只需要将供应商的Jar放在类路径中,然后DriverManager就可以自动检测并加载驱动程序
- 就是上面的第三方jar包导入步骤
- JDBC版本
- JDBC 4.0包含在Java SE 6中
- JDBC 4.1包含在Java SE 7中
- JDBC 4.2包含在Java SE 8中
- JDBC 4.3包含在Java SE 9中
- 这里说的是JDBC的版本,是嵌入在JDK中的代码,不是mysql等其他厂商的数据库版本
- 即:从Java SE 7开始,就可以省略第一步注册Driver的代码了
Statement
Statement的常用API
- ResultSet executeQuery(String sql)
- 执行DQL语句
- int executeUpdate(String sql)
- 执行DML、DDL语句
- 如果是DML语句,返回值代表影响的记录数量;如果数据库没有任何返回值,通常返回0
ResultSet 的常用API
- boolean next()
- 让游标指向下一行。如果指向的这行有数据,就返回true,否则,返回false
- XX getXX(int columnIndex) 、XX getXX(String columnLabel)
- 获取当前行(游标指向的那行) 某一列的数据
- columnIndex的数值从1开始
- XX指的是数据类型,比如int、double…
-
代码举例
public class Main { private static final String driverClassName = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/zh_info"; private static final String USERNAME = "root"; private static final String PASSWORD = "root"; public static void main(String[] args) throws Exception { try(Connection connection = DriverManager.getConnection(URL,USERNAME,PASSWORD); Statement statement = connection.createStatement() ){ String sql = "SELECT * FROM student"; ResultSet rs = statement.executeQuery(sql); rs.next();//第一条数据,让游标指向第一条记录 //获取第一条数据的age值 System.out.println(rs.getInt("age")); System.out.println(rs.getString("name")); //列的索引,对应的值,从1开始 System.out.println(rs.getInt(2)); } } }
PreparedStatement
- PreparedStatement接口继承自Statement接口
- 建议使用PreparedStatement替代Statement
- PreparedStatement的优点
- 可以防止SQL注入
- 执行速度比Statement快
- 支持批量处理
-
代码举例:
private static void login(String username,String password) throws Exception { try(Connection connection = DriverManager.getConnection(URL,USERNAME,PASSWORD); Statement statement = connection.createStatement() ){ String sql = "SELECT * FROM user WHERE username = '" + username+"' AND password = '"+ password + "'"; //SELECT * FROM user WHERE username = 'xxxx' AND password = '' OR '1' = '1' System.out.println(sql); ResultSet rs = statement.executeQuery(sql); if (rs.next()){ System.out.println("登录成功"); }else { System.out.println("登录失败"); } } } //main函数 //SQL注入 String username = "xxxx";//真正的账号是zhangsan String password = "' OR '1' = '1";//真正的密码是123 login(username,password); //结果是:登录成功
- 这么一些,那么拼接后的sql语句为:
SELECT * FROM user WHERE username = 'xxxx' AND password = '' OR '1' = '1'
,那么永远成立,因此怎么都登录成功
- 这么一些,那么拼接后的sql语句为:
-
使用PreparedStatement
private static void login2(String username,String password) throws Exception { String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; try(Connection connection = DriverManager.getConnection(URL,USERNAME,PASSWORD); PreparedStatement pstat = connection.prepareStatement(sql); ) { //设置上面的那两个?占位 pstat.setString(1,username); pstat.setString(2,password); ResultSet rs = pstat.executeQuery(); if (rs.next()){ System.out.println("登录成功"); }else { System.out.println("登录失败"); } } } //结果是登录失败
- 这么写会直接让?占位符的内容进行字符串转义,并不是直接拼接,防止注入
JDBC的封装(重点!!!)
- 依然用第一节的案例用户列表维护来讲解
- 将mysql的jdbc驱动包导入到项目(javaEE)中
封装1
- 将这些字符串常量抽取到一个类中,成为类成员常量
- 2个servlet(ListServlet/SaveServlet)都有数据处理的代码:连接数据库、查询数据、添加数据,将数据库处理的代码封装到DAO(Data Access Object)中,专门用于处理数据的对象
- 在项目目录下建立一个与bean同级的文件夹dao文件夹,里面专门用于存放每个表对应的dao对象
-
封装一个常量Constants
package com.zh.crm; public class Constants { public static final String DRIVERCLASSNAME = "com.mysql.jdbc.Driver"; public static final String URL = "jdbc:mysql://localhost:3306/crm"; public static final String USERNAME = "root"; public static final String PASSWORD = "root"; }
-
CustomerDao类的封装
public class CustomerDao { //保存用户信息 public boolean save(Customer customer){ try { String sql = "INSERT INTO customer(name, age, height) VALUES(?, ?, ?)"; Class.forName(Constants.DRIVERCLASSNAME); try(Connection con = DriverManager.getConnection(Constants.URL,Constants.USERNAME,Constants.PASSWORD); PreparedStatement stam = con.prepareStatement(sql)){ stam.setString(1,customer.getName()); stam.setInt(2,customer.getAge()); stam.setDouble(3,customer.getHeight()); return stam.executeUpdate() > 0; } }catch (Exception e){ e.printStackTrace(); return false; } } //获取用户信息 public List<Customer> list(){ try { String sql = "SELECT id ,name, age, height FROM customer"; Class.forName(Constants.DRIVERCLASSNAME); try(Connection con = DriverManager.getConnection(Constants.URL,Constants.USERNAME,Constants.PASSWORD); PreparedStatement stam = con.prepareStatement(sql); ResultSet rs = stam.executeQuery()){ List<Customer> customers = new ArrayList<>(); while (rs.next()){ Customer customer = new Customer(); customer.setAge(rs.getInt("age")); customer.setId(rs.getInt("id")); customer.setName(rs.getString("name")); customer.setHeight(rs.getDouble("height")); customers.add(customer); } return customers; } }catch (Exception e){ e.printStackTrace(); return null; } } }
-
将ListServlet的dopost中代码改造如下:
private final CustomerDao dao = new CustomerDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 从数据库获取所有数据 request.setAttribute("customers",dao.list()); request.getRequestDispatcher("/page/list.jsp").forward(request,response); }
-
将SaveServlet中的dopost中代码改造如下:
private final CustomerDao dao = new CustomerDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); //获取客户端发送的参数 Customer customer = new Customer(); customer.setName(request.getParameter("name")); customer.setAge(Integer.parseInt(request.getParameter("age"))); customer.setHeight(Double.parseDouble(request.getParameter("height"))); if (dao.save(customer)){ //重定向到list response.sendRedirect("/crm/list"); }else { //重定向到失败 request.setAttribute("error","保存用户信息失败"); request.getRequestDispatcher("/page/error.jsp").forward(request,response); } }
封装2
- 当有多个dao类时,每个类都有list与save方法,每个dao类都连接同一个数据库,那么每个类都要写很多的重复代码,那么就可以抽出一个工具类,把相同的代码抽取出来
- 在项目目录下新建一个util文件夹与dao同级,然后新建一个类Dbs(在Java中结尾为s的类通常是工具类,比如Arrays)
-
Dbs类:
public class Dbs { //这几个常量可以写在这里面,Constants类就不需要了 private static final String DRIVERCLASSNAME = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/crm"; private static final String USERNAME = "root"; private static final String PASSWORD = "root"; /** * 执行DML、DDL语句 * @return */ public static int update(String sql,Object ...args){ try { Class.forName(DRIVERCLASSNAME); try(Connection con = DriverManager.getConnection(URL,USERNAME,PASSWORD); PreparedStatement stam = con.prepareStatement(sql)){ //设置参数 for (int i = 0; i < args.length; i++) { stam.setObject(i+1,args[i]); } return stam.executeUpdate(); } }catch (Exception e){ e.printStackTrace(); return 0; } } /** * 查询 * @param sql * @param mapper * @param args 可变参数 * @param <T> * @return */ public static <T> List<T> query(String sql,RowMapper<T> mapper,Object ...args){ if (mapper == null) return null; try { Class.forName(DRIVERCLASSNAME); try(Connection con = DriverManager.getConnection(URL,USERNAME,PASSWORD); PreparedStatement stam = con.prepareStatement(sql)){ //设置参数 for (int i = 0; i < args.length; i++) { stam.setObject(i+1,args[i]); } ResultSet rs = stam.executeQuery(); List<T> array = new ArrayList<>(); int row = 0; while (rs.next()){ //rs->bean,数据转模型 array.add(mapper.map(rs,row++)); } rs.close(); return array; } }catch (Exception e){ e.printStackTrace(); return null; } } /** * 该接口用来执行每一行数据到bean的映射 * @param <T> */ public interface RowMapper<T>{ //throw:允许抛出异常 T map(ResultSet ts,int row) throws Exception; } }
-
CustomerDao类中的save、list方法
//保存用户信息,只需要传sql语句,跟参数 public boolean save(Customer customer){ String sql = "INSERT INTO customer(name, age, height) VALUES(?, ?, ?)"; return Dbs.update(sql,customer.getName(),customer.getAge(),customer.getHeight()) > 0; } //获取用户信息 public List<Customer> list(){ String sql = "SELECT id ,name, age, height FROM customer"; //lama表达式 return Dbs.query(sql,(rs,row) -> { Customer customer = new Customer(); customer.setAge(rs.getInt("age")); customer.setId(rs.getInt("id")); customer.setName(rs.getString("name")); customer.setHeight(rs.getDouble("height")); return customer; }); //匿名对象 // return Dbs.query(sql,new Dbs.RowMapper<Customer>() { // @Override // public Customer map(ResultSet rs, int row) throws Exception { // Customer customer = new Customer(); // customer.setAge(rs.getInt("age")); // customer.setId(rs.getInt("id")); // customer.setName(rs.getString("name")); // customer.setHeight(rs.getDouble("height")); // return customer; // } // }); }
- 疑问?
- 按照上面的封装,如果某天数据库的密码修改了,那么就需要重新修改Dbs里面的字符串常量,然后重新打包发布到tomcat
- 那么有没有一种方法可以将常用的变量设置成动态的,不用再次发布就可以呢? —-配置文件
配置文件
- 一些经常动态修改的值, 建议放入到配置文件中, 不要写死在Java代码中
- Java中常见的配置文件
- properties:比较单一, 适合量小、简单的配置
- xml:比较灵活, 适合量大、复杂的配置
- 新建完项目web/WEB-INF目录下有个web.xml就是
- properties文件内容格式
- 用于存放键值对
- key、value的分隔符是=、:
- 建议分隔符左右不要留空格
#、!
开头是单行注释- 可以用反斜线\连接多行内容:一行内容太多可以分行写
封装3
- 在上面项目的src文件夹下新建一个properties
- 右击src->new->resource bundle(资源包)->名称输入:db ->点击ok 会自动生成一个db.properties
- 注意:必须建立到src文件夹中,只有这样项目编译后的class文件夹的第一级子目录才会有这个db.properties文件
-
编译后的项目目录
out artifacts 04_crm index.jsp page(前端资源文件) WEB-INF classes com(一级一级包名,最后是java的class字节码) db.properties ... lib web.xml(默认的)
- 可以看到db.properties资源文件与com中的java字节码同级
-
db.properties的键值对如下:
#zhushi url=jdbc:mysql://localhost:3306/crm username=root password=root
-
Dbs工具类的代码如下:
public class Dbs { private static String url; private static String username; private static String password; //类第一次加载的时候读取db.properties static { try(InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties")) { //Dbs.class.getClassLoader():是获取到Dbs类编译后的classes目录下,在classes目录下有一个com文件夹(存放java编译后的calss代码),还有一个db.properties文件,这样就可直接在该目录下获取db.properties文件的流数据了 //InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties"); //java提供的API Properties properties = new Properties(); properties.load(is); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); }catch (Exception e){ e.printStackTrace(); } } //下面update、query代码不变,只是这句变了 ... Connection con = DriverManager.getConnection(url,username,password); ... }
-
如果用xml,同样在src文件夹下新建一个db.xml,然后在Dbs类中读取这个xml文件,然后解析内容。db.xml文件内容如下
<?xml version="1.0" encoding="UTF-8"?> <db> <url>jdbc:mysql://localhost:3306/crm</url> <username>root</username> <password>root</password> </db>
怎么体现可以自动替换不用重新打包部署呢?
- 上面讲过db.properties编译后,会直接原样复制到classes文件夹下的第一级目录下
- 如果修改db.properties文件夹的内容,直接替换即可,不需要重新打包部署
封装4
- 每一个请求都对应一个servlet,比较繁琐,那么如何是多个请求对应一个servlet呢?
-
一个路径下对应多个子路径,一个servlet只监听这个主路径
http://localhost:8081/crm/customer/list http://localhost:8081/crm/customer/save http://localhost:8081/crm/customer/remove
-
可以新建一个CustomerServlet,只监听
http://localhost:8081/crm/customer
-
- 代码举例
- add.html中的action要修改为:
/crm/customer/save
-
CustomerServlet代码如下:
@WebServlet("/customer/*") public class CustomerServlet extends HttpServlet { private final CustomerDao dao = new CustomerDao(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //反射 //获取当前类中声明的所有方法,即使是private //Method[] methods = getClass().getDeclaredMethods(); //获取当前类所有方法,包括父类的所有public方法 //Method[] methods = getClass().getMethods(); //获取成员变量 //getClass().getFields(); //getClass().getDeclaredFields(); //获取方法:方法名、参数 //Method method = getClass().getMethod(methodName,Integer.class,String.class); try { String uri = request.getRequestURI(); String[] comps = uri.split("/"); String methodName = comps[comps.length - 1]; //System.out.println(Arrays.toString(comps)); //获取方法实例 Method method = getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class); //方法调用 method.invoke(this,request,response); } catch (Exception e) { e.printStackTrace(); } } public void save(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ request.setCharacterEncoding("UTF-8"); //获取客户端发送的参数 Customer customer = new Customer(); customer.setName(request.getParameter("name")); customer.setAge(Integer.parseInt(request.getParameter("age"))); customer.setHeight(Double.parseDouble(request.getParameter("height"))); if (dao.save(customer)){ //重定向到list response.sendRedirect("/crm/customer/list"); }else { //重定向到失败 request.setAttribute("error","保存用户信息失败"); request.getRequestDispatcher("/page/error.jsp").forward(request,response); } } public void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ //1. 从数据库获取所有数据 request.setAttribute("customers",dao.list()); request.getRequestDispatcher("/page/list.jsp").forward(request,response); } public void remove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ } }
- add.html中的action要修改为:
数据库连接池
- 数据库连接池,可以提高访问数据库的性能,负责创建、分配、管理和释放数据库连接
- 上面的Dbs的代码中,每次调用update、query方法时都是从新连接数据库,然后访问数据库,访问完断开连接,每次都是这样过程,非常消耗性能
- 基本思想
- 创建:在初始化时,创建一定数量的数据库连接对象存储在内存中
- 分配:当需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已创建的空闲连接对象
- 管理:使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用
- 释放:当连接的空闲时间已超过最大空闲时间时,将会释放掉该连接
- 可以通过设置参数来控制初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间
- 可以通过其自身的管理机制来监视数据库连接的数量、使用情况
- Java中常见的开源的数据库连接池
- C3P0、Proxool、DBCP、BoneCP、Druid等
Druid
- 产自阿里巴巴,项目地址:https://github.com/alibaba/druid/
- jar包下载:https://mvnrepository.com/artifact/com.alibaba/druid
- 专门下载jar包的网站(常用jar包下载地址)
- 使用
- 下载druid-1.2.2.jar、druid-1.2.2-sources.jar(源码) 这2个包
- 将druid-1.2.2.jar导入lib中,然后加载到项目
封装5
-
src下新增一个druid.properties文件
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/crm username=root password=root initialSize=5 maxActive=10 maxWait=5000
-
Dbs封装如下
public class Dbs { private static DataSource ds; //类第一次加载的时候读取db.properties static { try(InputStream is = Dbs.class.getClassLoader().getResourceAsStream("druid.properties")) { Properties properties = new Properties(); properties.load(is); //创建数据库连接池 ds = DruidDataSourceFactory.createDataSource(properties); }catch (Exception e){ e.printStackTrace(); } } /** * 执行DML、DDL语句 * @return */ public static int update(String sql,Object ...args){ try { //注意:!!!这句话不能放到下面的try()中,如果放进去会自动调用close关闭连接,这不需要关闭,连接池自动管理 Connection con = ds.getConnection(); try(PreparedStatement stam = con.prepareStatement(sql)){ ... } }catch (Exception e){ ... } } public static <T> List<T> query(String sql,RowMapper<T> mapper,Object ...args){ if (mapper == null) return null; try { Connection con = ds.getConnection(); try(PreparedStatement stam = con.prepareStatement(sql)){ ... } }catch (Exception e){ ... } } ... }
Spring JDBC
- Spring JDBC框架可以帮助开发者节省大量开发工作,自动去处理一些低级细节,比如
- 异常处理、打开和关闭资源(Connection、Statement、ResultSet)
- 需要的jar包(1依赖于2、3、4、5)
- spring-jdbc
- spring-beans
- spring-core
- spring-tx
- spring-jcl
- 可以到https://mvnrepository.com去下载
Spring JDBC 核心类:JdbcTemplate
- 构造方法
public JdbcTemplate(DataSource dataSource)
- 执行DDL、DML语句
int update(String sql,Object... args)
- 执行DQL语句
<T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
<T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
封装6
- 下载Spring JDBC对应的5个包,导入到项目lib中
-
JdbcTemplate可以替换原来Dbs中封装的update跟query方法,则改造后如下:
public class Dbs { private static JdbcTemplate tpl; //类第一次加载的时候读取db.properties static { try(InputStream is = Dbs.class.getClassLoader().getResourceAsStream("druid.properties")) { Properties properties = new Properties(); properties.load(is); //创建连接池 DataSource ds = DruidDataSourceFactory.createDataSource(properties); //连接池与spring jdbc无缝结合,将连接池给spring jdbc tpl = new JdbcTemplate(ds); }catch (Exception e){ e.printStackTrace(); } } public static JdbcTemplate getTpl() { return tpl; } }
-
CustomerDao改造后如下:
public class CustomerDao { //保存用户信息 public boolean save(Customer customer){ String sql = "INSERT INTO customer(name, age, height) VALUES(?, ?, ?)"; List<Object> args = buildArgs(customer); return Dbs.getTpl().update(sql,args.toArray()) >0; // return Dbs.getTpl().update(sql,customer.getName(),customer.getAge(),customer.getHeight()) > 0; } //获取用户信息 public List<Customer> list(){ String sql = "SELECT id ,name, age, height FROM customer"; //告诉BeanPropertyRowMapper对象,将rs映射成Customer对象,本质是利用反射 return Dbs.getTpl().query(sql,new BeanPropertyRowMapper<>(Customer.class)); //lama表达式 // return Dbs.getTpl().query(sql,(rs,row) -> { // Customer customer = new Customer(); // customer.setAge(rs.getInt("age")); // customer.setId(rs.getInt("id")); // customer.setName(rs.getString("name")); // customer.setHeight(rs.getDouble("height")); // return customer; // }); } /** * 删除Customer * @param id * @return */ public boolean remove(Integer id){ String sql = "DELETE FROM customer WHERE id = ?"; return Dbs.getTpl().update(sql,id)>0; } /** * 查询Customer * @param id * @return */ public Customer find(Integer id){ String sql = "SELECT id ,name, age, height FROM customer WHERE id = ?"; //直接映射成Customer对象 return Dbs.getTpl().queryForObject(sql,new BeanPropertyRowMapper<>(Customer.class),id); } /** * 更新Customer * @param customer * @return */ public boolean update(Customer customer){ String sql = "UPDATE customer SET name = ?, age = ?, height = ? WHERE id = ?"; List<Object> args = buildArgs(customer); args.add(customer.getId()); return Dbs.getTpl().update(sql,args.toArray()) >0; // return Dbs.getTpl().update(sql,customer.getName(),customer.getAge(),customer.getHeight(),customer.getId()) > 0; } private List<Object> buildArgs(Customer customer){ List<Object> args = new ArrayList<>(); args.add(customer.getName()); args.add(customer.getAge()); args.add(customer.getHeight()); return args; } }
-
CustomerServlet改造如下
@WebServlet("/customer/*") public class CustomerServlet extends HttpServlet { private final CustomerDao dao = new CustomerDao(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { request.setCharacterEncoding("UTF-8"); String uri = request.getRequestURI(); String[] comps = uri.split("/"); String methodName = comps[comps.length - 1]; //获取方法实例 Method method = getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class); //方法调用 method.invoke(this,request,response); } catch (Exception e) { e.printStackTrace(); //重定向到失败 forwardError(request,response,"访问路径有问题"); } } public void save(HttpServletRequest request, HttpServletResponse response) throws Exception{ //获取客户端发送的参数 Customer customer = newCustomer(request); if (dao.save(customer)){ //重定向到list response.sendRedirect("/crm/customer/list"); }else { //重定向到失败 forwardError(request,response,"保存用户信息失败"); } } public void list(HttpServletRequest request, HttpServletResponse response) throws Exception{ //1. 从数据库获取所有数据 request.setAttribute("customers",dao.list()); request.getRequestDispatcher("/page/list.jsp").forward(request,response); } public void remove(HttpServletRequest request, HttpServletResponse response) throws Exception{ Integer id = Integer.valueOf(request.getParameter("id")); if (dao.remove(id)){ //删除成功,刷新 response.sendRedirect("/crm/customer/list"); }else { //重定向到失败 forwardError(request,response,"删除用户信息失败"); } } public void edit(HttpServletRequest request, HttpServletResponse response) throws Exception{ Integer id = Integer.valueOf(request.getParameter("id")); Customer customer = dao.find(id); //转发给jsp request.setAttribute("customer",customer); request.getRequestDispatcher("/page/update.jsp").forward(request,response); } public void update(HttpServletRequest request, HttpServletResponse response) throws Exception{ //获取客户端发送的参数 Customer customer = newCustomer(request); customer.setId(Integer.valueOf(request.getParameter("id"))); if (dao.update(customer)){ //删除成功,刷新 response.sendRedirect("/crm/customer/list"); }else { //重定向到失败 forwardError(request,response,"更新用户信息失败"); } } private void forwardError(HttpServletRequest request, HttpServletResponse response,String error)throws ServletException, IOException{ //重定向到失败 request.setAttribute("error",error); request.getRequestDispatcher("/page/error.jsp").forward(request,response); } private Customer newCustomer(HttpServletRequest request) throws Exception{ //这些可以用反射 Customer customer = new Customer(); // customer.setName(request.getParameter("name")); // customer.setAge(Integer.parseInt(request.getParameter("age"))); // customer.setHeight(Double.parseDouble(request.getParameter("height"))); //使用apache封装好的轮子,专门用户数据转模型:beanutils /* 导入这3个jar包 commons-beanutils-1.9.4.jar commons-collections-3.2.2.jar commons-logging-1.2.jar * */ BeanUtils.populate(customer,request.getParameterMap()); return customer; } }
-
用户列表页面list.jsp新增编辑、删除功能
//list.jsp //表头 <tr> <th>姓名</th> <th>年龄</th> <th>身高</th> <th>操作</th> </tr> //每行内容 <c:forEach items="${customers}" var="customer" varStatus="s"> <tr> <td>${customer.name}</td> <td>${customer.age}</td> <td>${customer.height}</td> <td> <a href="/crm/customer/edit?id=${customer.id}">编辑</a> <a href="/crm/customer/remove?id=${customer.id}">删除</a> </td> </tr> </c:forEach>
-
新增编辑页面update.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>编辑客户</title> </head> <body> <form action="/crm/customer/update" method="post"> <input type="hidden" name="id" value="${customer.id}"> <div>姓名 <input type="text" name="name" value="${customer.name}"></div> <div>年龄 <input type="text" name="age" value="${customer.age}"></div> <div>身高 <input type="text" name="height" value="${customer.height}"></div> <div><button type="submit">更新</button></div> </form> </body> </html>