예전에는 사용자 정보를 저장하기 위해 데이터베이스를 나타내는 xml 파일을 만들었습니다. 이제 데이터베이스 관련 지식을 배웠으므로 xml을 데이터베이스로 바꾸고 데이터베이스 응용 프로그램으로 업그레이드해야 합니다.
이전 프로젝트를 복사해서 복사할 때 이전 프로젝트 이름이 day09_user라고 가정하고 이제 복사해서 복사해서 프로젝트 이름을 day14_user로 바꾸고 이제 tomcat 서버에 직접 배포하면 day14_user JavaWeb 애플리케이션 매핑이 됩니다. 가상 디렉터리는 여전히 "/day09_user"이며 동일한 이름 "/day14_user"를 가진 가상 디렉터리에 매핑되지 않습니다. 이는 종종 간과되는 문제입니다.
데이터베이스 드라이버 가져오기
애플리케이션에 해당하는 라이브러리 및 테이블 생성
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
4 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
db.properties에 MySQL 데이터베이스의 연결 정보를 작성합니다. 코드는 다음과 같습니다.
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
6
8
9
10
11
12
13
14
15
16
17
공개 사용자 찾기(문자열 사용자 이름, 문자열 비밀번호)에 중점을 둘 것입니다.
로그인 방법. public User find(String username, String Password)
메서드를 다시 작성합니다. 코드는 다음과 같습니다. @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); } }1234567891011121314151617181920212223242526272829Java에서 예외 처리가 정말 귀찮습니다. 예외 처리 원칙에 대해 이야기해 보겠습니다.
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
public User find(String 사용자 이름, 문자열 비밀번호)
메소드를 다음과 같이 수정하세요. @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开发模式的用户登录注册的升级改造圆满完成。
위 내용은 Servlet+JSP+JavaBean 개발 모델 기반 사용자 로그인 등록을 위한 업그레이드 튜토리얼에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!