これまではユーザー情報を保存するためにデータベースを表す 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
8
9
10
11
MySQL データベースの接続情報を db.properties に書き込みます。コードは次のとおりです:
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
1
3
4
5
6
UserDao インターフェースのすべてのメソッドを書き換えて、UserDao の実装クラスを作成する必要があります。 aoインターフェイスのcn.itcast.dao.impl パッケージ ——UserDaoJdbcImpl (以下に示すとおり):
public User find(String username, String password) に焦点を当てます。
ログインメソッド。 最初にステートメント オブジェクトを使用して、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); } }1234567891011121314151617181920212223242526272829
Java 言語設計者 - Commander Gao 氏が設計した Java 言語にはコンパイル時例外があり、 Java 以降 この言語は彼によって設計されたため、コンパイル時の例外も合理的に存在するはずだと彼は考えています。 public User find(String username, String password)
登录方法。
我们首先使用statement对象重写public User find(String username, String password)
方法,代码如下:
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
在java中关于对异常的处理真是太mb的烦了,现在总结一下对异常的处理原则。
在学界关于对java异常的处理吵的热火朝天,主要分为三大门派:
java语言设计者——高司令,他设计出来的java语言有编译时异常和运行时异常,既然java语言都是他设计出来的,所以他认为编译时异常也是必须合理存在的。
《Thinking In Java》的作者认为java语言编译时异常就是垃圾,异常都应转为运行时异常抛出去。
Spring的作者柔和了以上2人的观点,他就说——你这个有程序有异常,你拿到这个异常,怎么做呢?就看上一层程序能不能处理?如果不能处理,就转为运行时异常抛出去,如果能处理,就转为编译时异常直接往上抛出去。
所以,最好我们应采用Spring的作者的观点,那我们的处理方法就是:你这个有程序有异常,你拿到这个异常,怎么做呢?就看异常你希不希望上一层程序处理?如果你不希望上一层程序处理,免得给上一层程序带来麻烦,就转为运行时异常抛出去,如果你希望上一层程序处理,就转为编译时异常直接往上抛出去。
在实际开发中,最好每一个层都编写一个自定义异常,例如在Dao层(数据访问层)自定义一个异常类——DaoException。
在cn.itcast.exception包中创建异常类DaoException,如下:
DaoException类代码如下:
@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
我们要自定义一个Dao异常抛出去,为什么呢?自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名,就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。
接着修改UserDaoJdbcImpl类中的public User find(String username, String password)
「Thinking In Java」の著者は、Java 言語のコンパイル時例外はゴミであり、例外は実行時例外に変換されてスローされるべきだと考えています。
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
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) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 /* * 自定义一个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 中国語 Web サイトの他の関連記事を参照してください。