We used to create xml files representing databases to save user information. Now that we have learned database-related knowledge, we should replace xml with databases and upgrade to database applications.
When we copy and copy the previous project, assuming that the previous project name is day09_user, now make a copy and copy it, rename the project to day14_user, and now deploy it directly on the tomcat server, then the day14_user JavaWeb The virtual directory mapped by the application is still "/day09_user" and is not mapped to a virtual directory with the same name "/day14_user". This is a problem that is often overlooked. To solve this problem, you can do the following:
Import database driver
Create the corresponding libraries and tables for the application
create database day14_user;use day14_user;create table users ( id varchar(40) primary key, username varchar(40) not null unique, password varchar(40) not null, email varchar(100) not null unique, birthday date, nickname varchar(40) not null);
1
2
3
In db. Write the connection information of the MySQL database in properties. The code is as follows:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/day14_userusername=root password=yezi#driver=oracle.jdbc.driver.OracleDriver#url=jdbc:oracle:thin:@localhost:1521:orcl#username=system#password=111
##5
##14
15
16
Rewriting UserDao
Since what we wrote at the beginning was not the final code, we will focus on the
@Override public User find(String username, String password) { Connection conn = null; Statement st = null; // PreparedStatement预防SQL注入 ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "select * from users where username='"+username+"' and password='"+password+"'"; rs = st.executeQuery(sql); if (rs.next()) { User user = new User(); user.setBirthday(rs.getDate("birthday")); user.setEmail(rs.getString("email")); user.setId(rs.getString("id")); user.setNickname(rs.getString("nickname")); user.setPassword(rs.getString("password")); user.setUsername(rs.getString("username")); return user; } return null; } catch (Exception e) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 throw new RuntimeException(e); } finally { JdbcUtils.release(conn, st, rs); } }1234567891011121314151617181920212223242526272829
The author of "Thinking In Java" believes that Java language compile-time exceptions are garbage, and exceptions should be converted into runtime exceptions and thrown out.
You have a program that has an exception. If you get this exception, what should you do? Woolen cloth? It just depends on whether you want the upper layer of the program to handle the exception? If you don't want the upper-level program to handle it, so as not to cause trouble to the upper-level program, turn it into a runtime exception and throw it. If you want the upper-level program to handle it, turn it into a compile-time exception and throw it directly. go
.public class DaoException extends RuntimeException { public DaoException() { // TODO Auto-generated constructor stub } public DaoException(String message) { super(message); // TODO Auto-generated constructor stub } public DaoException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } public DaoException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); // TODO Auto-generated constructor stub } }123456789101112131415161718192021222324252627
We want to customize a Dao exception to throw, why? The biggest advantage of customizing a Dao exception to throw is that I throw this exception. When someone receives this exception, as soon as he sees the class name of the exception, he can know which layer has the problem, and he can Quickly locate this layer to find the problem. It is best to have a custom exception for each layer.
Then modify the
public User find(String username, String password)
method in the UserDaoJdbcImpl class as:
@Override public User find(String username, String password) { Connection conn = null; Statement st = null; // PreparedStatement预防SQL注入 ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "select * from users where username='"+username+"' and password='"+password+"'"; rs = st.executeQuery(sql); if (rs.next()) { User user = new User(); user.setBirthday(rs.getDate("birthday")); user.setEmail(rs.getString("email")); user.setId(rs.getString("id")); user.setNickname(rs.getString("nickname")); user.setPassword(rs.getString("password")); user.setUsername(rs.getString("username")); return user; } return null; } catch (Exception e) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 /* * 自定义一个Dao异常抛出去,为什么呢? * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名, * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。 */ throw new DaoException(e); } finally { JdbcUtils.release(conn, st, rs); } }1234567891011121314151617181920212223242526272829303132333435
我们以前在开发service层(service层对web层提供所有的业务服务)时,编写UserService接口的具体实现类——UserServiceImpl时,业务逻辑层和数据访问层是紧密联系在一起的,所以业务逻辑层和数据访问层要解耦(希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式),要解耦,有两种方法:
工厂模式
Spring
现在我们重点关注工厂模式。这时我们要定义一个Dao工厂,新建一个cn.itcast.factory包,在包中创建一个Dao工厂——DaoFactory,如下所示:
工厂一般要设计成单例的,为什么呢?
答:工厂设计成单例的,工厂的对象在内存中只有一个,目的是希望所有的Dao都由一个工厂来生产。假设不把工厂设计成单例的,将来Dao由不同的工厂来生产,你觉得所有的Dao由一个工厂来生产好,还是由不同的工厂来生产好?答案显然是由一个工厂来生产好,将来维护起来好维护。
我们先编写DaoFactory类的代码为:
public class DaoFactory { private Properties daoConfig = new Properties(); private DaoFactory() {} private static DaoFactory instance = new DaoFactory(); public static DaoFactory getInstance() { return instance; } // UserDao.class(接口类型) // DepartmentDao.class(接口类型) public <T> T createDao(Class<T> clazz) { // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); ...... } }123456789101112131415161718192021
以上的代码好吗?显然这样做并不好,每次别人调用createDao方法,都会去读一下配置文件dao.properties,但此配置文件在整个系统里面只要读取一次就好,没必要老去读,即以下这行代码只需运行一次。
DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");1
那么这行代码可以放到静态代码块里。
现在将以上代码修改为:
public class DaoFactory { static { DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); } private Properties daoConfig = new Properties(); private DaoFactory() {} private static DaoFactory instance = new DaoFactory(); public static DaoFactory getInstance() { return instance; } // UserDao.class(接口类型) // DepartmentDao.class(接口类型) public <T> T createDao(Class<T> clazz) { // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); ...... } }12345678910111213141516171819202122232425
以上这样做并没有不好,但是对于现在而言还有一种做法,DaoFactory这个类被设计成单例的,所以其构造函数仅执行一次,所以可放到DaoFactory这个类的构造函数里面。所以最后完整的代码如下:
public class DaoFactory { /* static { DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); } */ private Properties daoConfig = new Properties(); private DaoFactory() { InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); try { daoConfig.load(in); } catch (IOException e) { throw new RuntimeException(e); } } private static DaoFactory instance = new DaoFactory(); public static DaoFactory getInstance() { return instance; } // UserDao.class(接口类型) // DepartmentDao.class(接口类型) public <T> T createDao(Class<T> clazz) { // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); String className = daoConfig.getProperty(name); try { T dao = (T) Class.forName(className).newInstance(); // 创建实例对象 return dao; } catch (Exception e) { throw new RuntimeException(e); } } }123456789101112131415161718192021222324252627282930313233343536373839
然后在src目录下创建一个dao.properties文件,如下图所示:
在db.properties中内容如下所示:
UserDao=cn.itcast.dao.impl.UserDaoJdbcImpl1
实现service层和dao层的解耦,UserService接口的具体实现类——UserServiceImpl只须修改为:
// 对web层提供所有的业务服务public class BusinessServiceImpl { /* * 业务逻辑层和数据访问层要解耦——希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式 * 要解耦,有两张方法: * 1. 工厂模式 * 2. spring */ // private UserDao dao = new UserDaoJdbcImpl(); private UserDao dao = DaoFactory.getInstance().createDao(UserDao.class); // 对web层提供注册服务 public void register(User user) throws UserExistException { // 先判断当前要注册的用户是否存在 boolean b = dao.find(user.getUsername()); if(b) { /* * service层是由web层来调用的, * 发现当前要注册的用户已存在,要提醒给web层,web层给用户一个友好提示 * 希望web层一定要处理,处理之后给用户一个友好提示,所以抛一个编译时异常, * 抛运行时异常是不行的,因为web层可处理可不处理 */ throw new UserExistException(); // 发现要注册的用户已存在,则给web层抛一个编译时异常,提醒web层处理这个异常,给用户一个友好提示。 } else { user.setPassword(ServiceUtils.md5(user.getPassword())); dao.add(user); } } // 对web层提供登录服务 public User login(String username, String password) { // aaa 123 password = ServiceUtils.md5(password); // 要把密码md5一把再找 return dao.find(username, password); } }123456789101112131415161718192021222324252627282930313233343536
SQL注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。
例如,statement存在sql注入攻击问题,假如登录用户名采用' or 1=1 or name='
。
对于防范SQL注入,可以采用PreparedStatement取代Statement。
PreperedStatement是Statement的孩子,它的实例对象可以通过调用Connection.preparedStatement()方法获得,相对于Statement对象而言:
PreperedStatement可以避免SQL注入的问题。
Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可对SQL进行预编译,从而提高数据库的执行效率。
并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。
使用PreparedStatement改写UserDaoJdbcImpl类的public User find(String username, String password)
方法。
@Override public User find(String username, String password) { Connection conn = null; PreparedStatement st = null; // PreparedStatement预防SQL注入 ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "select * from users where username=? and password=?"; st = conn.prepareStatement(sql); // 预编译这条sql语句 st.setString(1, username); st.setString(2, password); rs = st.executeQuery(); if (rs.next()) { User user = new User(); user.setBirthday(rs.getDate("birthday")); user.setEmail(rs.getString("email")); user.setId(rs.getString("id")); user.setNickname(rs.getString("nickname")); user.setPassword(rs.getString("password")); user.setUsername(rs.getString("username")); return user; } return null; } catch (Exception e) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 throw new DaoException(e); } finally { JdbcUtils.release(conn, st, rs); } }12345678910111213141516171819202122232425262728293031
到此为止,我们就可以完整地写出UserDaoJdbcImpl类了,其完整代码如下:
public class UserDaoJdbcImpl implements UserDao { @Override public void add(User user) { Connection conn = null; // Statement st = null; PreparedStatement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "insert into users(id,username,password,email,birthday,nickname) values(?,?,?,?,?,?)"; st = conn.prepareStatement(sql); st.setString(1, user.getId()); st.setString(2, user.getUsername()); st.setString(3, user.getPassword()); st.setString(4, user.getEmail()); st.setDate(5, new java.sql.Date(user.getBirthday().getTime())); st.setString(6, user.getNickname()); // st = conn.createStatement(); // String sql = "insert into users(id,username,password,email,birthday,nickname) values('"+user.getId()+"','"+user.getUsername()+"','"+user.getPassword()+"','"+user.getEmail()+"','"+user.getBirthday().toLocaleString()+"','"+user.getNickname()+"')"; // int num = st.executeUpdate(sql); int num = st.executeUpdate(); if(num < 1) { throw new RuntimeException("注册用户失败!!!"); } } catch (Exception e) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 /* * 自定义一个Dao异常抛出去,为什么呢? * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名, * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。 */ /* * gosling Thinking In Java Spring * Spring作者——你这个有程序有异常,你拿到这个异常,怎么做呢?就看上一层程序能不能处理? * 如果不能处理,就转为运行时异常抛出去,如果能处理,就转为编译时异常直接往上抛出去。 * * 我们的处理方法——你这个有程序有异常,你拿到这个异常,怎么做呢?就看异常你希不希望上一层程序处理? * 如果你不希望上一层程序处理,免得给上一层程序带来麻烦,就转为运行时异常抛出去,如果你希望上一层程序处理,就转为编译时异常直接往上抛出去。 */ // throw new RuntimeException(e); throw new DaoException(e); } finally { JdbcUtils.release(conn, st, rs); } } // username='or 1=1 or username=' password="" @Override public User find(String username, String password) { Connection conn = null; PreparedStatement st = null; // PreparedStatement预防SQL注入 ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "select * from users where username=? and password=?"; st = conn.prepareStatement(sql); // 预编译这条sql语句 st.setString(1, username); st.setString(2, password); rs = st.executeQuery(); if (rs.next()) { User user = new User(); user.setBirthday(rs.getDate("birthday")); user.setEmail(rs.getString("email")); user.setId(rs.getString("id")); user.setNickname(rs.getString("nickname")); user.setPassword(rs.getString("password")); user.setUsername(rs.getString("username")); return user; } return null; } catch (Exception e) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 throw new DaoException(e); } finally { JdbcUtils.release(conn, st, rs); } } @Override public boolean find(String username) { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "select * from users where username=?"; st = conn.prepareStatement(sql); st.setString(1, username); rs = st.executeQuery(); if (rs.next()) { return true; } return false; } catch (Exception e) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 throw new DaoException(e); } finally { JdbcUtils.release(conn, st, rs); } } }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
面试题:Statement和PreparedStatement的区别。
答:
PreparedStatement是Statement的孩子。
PreparedStatement可以防止sql注入的问题。
PreparedStatement会对sql语句进行预编译,以减轻数据库服务器的压力。
就像xxx.java
→xxx.class
→JVM执行
一样,sql语句
→编译
→数据库执行
。
开发完数据访问层,一定要对程序已编写好的部分代码进行测试,做一步,测试一步,以免整个程序完成后由于页面太多或者是代码量太大给查找错误造成更大的负担!所以,在juint.test包下创建了一个UserDaoJdbcTest类。
UserDaoJdbcTest类的具体代码如下:
public class UserDaoJdbcTest { @Test public void testAdd() { User user = new User(); user.setBirthday(new Date()); user.setEmail("bb@sina.com"); user.setId("2142354354"); user.setNickname("李子"); user.setUsername("bbbb"); user.setPassword("123"); UserDao dao = new UserDaoJdbcImpl(); dao.add(user); } @Test public void testFind() { UserDao dao = new UserDaoJdbcImpl(); User user = dao.find("bbbb", "123"); // 在断点模式Watch System.out.println(user); } @Test public void testFindByUsername() { UserDao dao = new UserDaoJdbcImpl(); System.out.println(dao.find("bbbb")); } }12345678910111213141516171819202122232425262728
经测试,没发现任何错误。同样也要对业务逻辑层已编写好的部分代码进行测试,在juint.test包下创建了一个ServiceTest类。
ServiceTest类的具体代码如下:
public class ServiceTest { @Test public void testRegister() { User user = new User(); user.setBirthday(new Date()); user.setEmail("bb@sina.com"); user.setId("2142354354"); user.setNickname("李子"); user.setUsername("lizi"); user.setPassword("123"); BusinessServiceImpl service = new BusinessServiceImpl(); try { service.register(user); System.out.println("注册成功!!!"); } catch (UserExistException e) { System.out.println("用户已存在"); } } @Test public void testLogin() { BusinessServiceImpl service = new BusinessServiceImpl(); User user = service.login("lizi", "123"); System.out.println(user); } }12345678910111213141516171819202122232425262728
经测试,没发现任何错误。
到此,对基于Servlet+JSP+JavaBean开发模式的用户登录注册的升级改造圆满完成。
The above is the detailed content of Detailed explanation of the upgrade tutorial for user login registration based on Servlet+JSP+JavaBean development model. For more information, please follow other related articles on the PHP Chinese website!