- 数据库
- JDBC
- SQL注入
- 两表关系
- 事务
- 批处理
- 工具类
- 连接池
- Web
- Tomcat
- 基础的原理就不讲了,直接看Java中的操作
- 大部分数据库操作都是查询,复杂查询按照套路走
- 合并查询
- 内连查询
- 外连查询
-
Java应用通过JDBC API调用驱动,操作对应的数据库
-
核心组件
-
使用步骤(六步)
- 导包:import java.sql.*
- 初始化驱动
- 连接:DriverManager.getConnection()
- 执行查询
- 提取数据
- 关闭连接
// 在project structure中创建Library引入jar包 public static void main(String[] args) { Connection conn = null; Statement stat = null; ResultSet result = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 数据库名称 falsk, 注意格式 // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); // Home End // 3. 定义SQL stat = conn.createStatement(); result = stat.executeQuery("select * from short_url;"); // int effect = stat.executeUpdate("insert into short_url(token, url) values('test2', 'http://porn.com')");// 插入,返回受影响行数 // 4. 取结果 while (result.next()) { System.out.println("id:"+result.getString("id")+ " token:"+result.getString("token")); } // 如果是增删改: // if (effect > 0) { // System.out.println("执行成功!"); // }else { // System.out.println("执行失败!"); // } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { // 5. 关闭连接 try { if (result != null) { result.close(); } if (stat != null) { stat.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } }
- 在执行的SQL语句中加入一些特殊字符,让引擎误识别,能绕开权限获取数据,造成数据的不安全
- 避免SQL注入的方法:创建预状态通道
- 在使用JDBC的第三步,通过createStatement创建的是状态通道,现在使用PreparedStatement创建预通道
public static void main(String[] args) { Connection conn = null; Statement stat = null; PreparedStatement pstmt = null; ResultSet result = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 数据库名称 falsk, 注意格式 // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); // Home End // 3. 定义SQL并执行 // stat = conn.createStatement(); String sql = "select * from short_url where id=?"; // 所有的参数使用 ? 占位 pstmt = conn.prepareStatement(sql); String id = "1"; // 替换占位符 pstmt.setString(1, id); // 使用字符串 result = pstmt.executeQuery(); // result = stat.executeQuery("select * from short_url;"); // 查询 // 4. 取结果 while (result.next()) { System.out.println("id:"+result.getString("id")+ " token:"+result.getString("token")); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { // 5. 关闭连接 try { if (result != null) { result.close(); } if (pstmt != null) { stat.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } }- 预状态通道会先编译sql语句,再去执行,比statement执行效率高
- 预状态通道支持占位符?,给占位符赋值的时候,位置从1开始
- 预状态通道可以防止sql注入,原因:预状态通道在处理值的时候全部以字符串的方式处理
- 数据库通过外键建立两表关系
- 实体类通过属性的方式建立两表关系,类似对象关系映射ORB
- 类名=表名
- 属性名=字段名
- 使用下面的数据表练习
CREATE TABLE `student` ( `stuid` int(11) NOT NULL AUTO_INCREMENT, `stuname` varchar(255) DEFAULT NULL, `teacherid` int(11) DEFAULT NULL, PRIMARY KEY (`stuid`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `student` VALUES ('1', 'aaa', '3'); INSERT INTO `student` VALUES ('2', 'bb', '1'); INSERT INTO `student` VALUES ('3', 'cc', '3'); INSERT INTO `student` VALUES ('4', 'dd', '1'); INSERT INTO `student` VALUES ('5', 'ee', '1'); INSERT INTO `student` VALUES ('6', 'ff', '2'); DROp TABLE IF EXISTS `teacher`; CREATE TABLE `teacher` ( `tid` int(11) NOT NULL AUTO_INCREMENT, `tname` varchar(255) DEFAULT NULL, PRIMARY KEY (`tid`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `teacher` VALUES ('1', '张三老师'); INSERT INTO `teacher` VALUES ('2', '李四老师'); INSERT INTO `teacher` VALUES ('3', '王五'); -- teacher表是主表,一个tid对应多个学生,所以后面的Java代码中要多加个集合属性 - 接下来创建对应的类和属性,显然,这些类都是bean
public class Student { private int stuid; private String stuname; // 外键列一般不生成方法 private int teacherid; public int getStuid() { return stuid; } public void setStuid(int stuid) { this.stuid = stuid; } public String getStuname() { return stuname; } public void setStuname(String stuname) { this.stuname = stuname; } } public class Teacher { private int tid; private String tname; private Listlist=new ArrayList (); // 新增属性,一对多查询,保存学生信息 public int getTid() { return tid; } public void setTid(int tid) { this.tid = tid; } public String getTname() { return tname; } public void setTname(String tname) { this.tname = tname; } public List getList() { return list; } public void setList(List list) { this.list = list; } } - 创建dao层,定义一对多的查询接口和实现类(一个老师对应多个学生)
// TeacherDaoImpl.java public class TeacherDaoImpl implements TeacherDao { @Override public Teacher getById(int tid) { Connection conn = null; PreparedStatement pstmt = null; ResultSet result = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 数据库名称 falsk, 注意格式 // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); // Home End // 3. 定义SQL并执行 预通道 String sql = "select * from student s, teacher t where s.teacherid=t.tid and tid=?"; // 所有的参数使用 ? 占位 pstmt = conn.prepareStatement(sql); pstmt.setInt(1, tid); // 都要使用字符串,数据库引擎会解析 result = pstmt.executeQuery(); // 4. 取结果 Teacher teacher = new Teacher(); Liststudents = new ArrayList (); while (result.next()) { // 取出各自信息,注意顺序 teacher.setTid(result.getInt("tid")); teacher.setTname(result.getString("tname")); Student student = new Student(); student.setStuid(result.getInt("stuid")); student.setStuname(result.getString("stuname")); students.add(student); } teacher.setList(students); return teacher; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { // 5. 关闭连接 try { if (result != null) { result.close(); } if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } return null; } } // test/Teac.java public static void main(String[] args) { // 操作数据库都会定义一个dao层,这里的dao和sql包,类似MVC模型的M TeacherDao daot = new TeacherDaoImpl(); // Impl是具体实现,类似List和ArrayList Teacher teacher = daot.getById(1); System.out.println("老师姓名:"+teacher.getTname()); // 一查多 Liststudents = teacher.getList(); for (Student student:students) { System.out.println("tsname: "+student.getStuname()); } } - 多对一查询(每个学生都带上老师信息),也是类似的,在Student类中增加属性保存老师信息呗
// 增加属性,多对一查询 private Teacher teacher; public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; }- 一对多:一方中有一个多方的集合
- 多对一:多方中有一个一方的数据
- 一对一:用的比较少,就是写SQL的区别,其他一样,两个表都要新增保存对方数据的属性
- 多对多,例如学生和科目,用到如下表:
CREATE TABLE `middle` ( `middleid` int(11) NOT NULL AUTO_INCREMENT, `stuid` int(11) DEFAULT NULL, `subid` int(11) DEFAULT NULL, PRIMARY KEY (`middleid`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; INSERT INTO `middle` VALUES ('1', '1', '1'); INSERT INTO `middle` VALUES ('2', '1', '2'); INSERT INTO `middle` VALUES ('3', '1', '3'); INSERT INTO `middle` VALUES ('4', '1', '5'); INSERT INTO `middle` VALUES ('5', '2', '2'); INSERT INTO `middle` VALUES ('6', '3', '2'); INSERT INTO `middle` VALUES ('7', '4', '2'); INSERT INTO `middle` VALUES ('8', '5', '2'); INSERT INTO `middle` VALUES ('9', '6', '2'); DROp TABLE IF EXISTS `student`; CREATE TABLE `student` ( `stuid` int(11) NOT NULL AUTO_INCREMENT, `stuname` varchar(255) DEFAULT NULL, `teacherid` int(11) DEFAULT NULL, PRIMARY KEY (`stuid`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `student` VALUES ('1', '张三', '3'); INSERT INTO `student` VALUES ('2', '李四', '1'); INSERT INTO `student` VALUES ('3', '王五', '3'); INSERT INTO `student` VALUES ('4', '赵六', '1'); INSERT INTO `student` VALUES ('5', '花花', '1'); INSERT INTO `student` VALUES ('6', '潇潇', '2'); DROP TABLE IF EXISTS `subject`; CREATE TABLE `subject` ( `subid` int(11) NOT NULL AUTO_INCREMENT, `subname` varchar(255) DEFAULT NULL, PRIMARY KEY (`subid`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; INSERT INTO `subject` VALUES ('1', 'java'); INSERT INTO `subject` VALUES ('2', 'ui'); INSERT INTO `subject` VALUES ('3', 'h5'); INSERT INTO `subject` VALUES ('4', 'c'); INSERT INTO `subject` VALUES ('5', 'c++'); INSERT INTO `subject` VALUES ('6', 'c#');- 多对多要用到中间表middle;创建对应的类
- 在Student类和Subject类中增加相应的属性配置
// 配置多对多,增加属性 private List
subjects; public List getSubjects() { return subjects; } public void setSubjects(List subjects) { this.subjects = subjects; } public class Subject { private int subid; private String subname; // 配置多对多,新增学生集合 private Liststudents; public List getStudents() { return students; } public void setStudents(List students) { this.students = students; } public int getSubid() { return subid; } public void setSubid(int subid) { this.subid = subid; } public String getSubname() { return subname; } public void setSubname(String subname) { this.subname = subname; } } - dao层定义接口和实现类,并测试
// 代码很长,这里就放两个SQL // select * from student s, subject sub, middle m where s.stuid=m.stuid and sub.subid=m.subid and s.stuid=? // select * from subject sub, student s, middle m where s.stuid=m.stuid and sub.subid=m.subid and s.subid=? // 剩下的就是JDBC五步骤,分开来看还是用中间表实现了,一对多查询
- 事务特点:ACID(背)
- 一般设置手动提交
// 获取连接时 conn.setAutoCommit(false); // 不自动提交
- 可以设置保存点 savepoints,回滚时可以指定到某个点,而不是全部失败
public static void main(String[] args) { Connection conn = null; Statement stat = null; PreparedStatement pstmt = null; Savepoint sp1 = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); conn.setAutoCommit(false); // 不自动提交 // 3. 定义SQL并执行 String sql_a = "insert into middle(stuid, subid) values(6, 3)"; pstmt = conn.prepareStatement(sql_a); int result = pstmt.executeUpdate(); // 设置一个保存点------------------ sp1 = conn.setSavepoint("sp1"); // 手动失败----------------------- System.out.println(5/0); String sql_b = "insert into middle(stuid, subid) values(2, 3)"; pstmt = conn.prepareStatement(sql_b); int result2 = pstmt.executeUpdate(); // 手动提交事务 conn.commit(); // 4. 取结果 if (result > 0) { System.out.println("执行成功!"); } else { System.out.println("执行失败!"); } } catch (Exception e) { // 注意这里要用父类Exception,不然不走这块的的异常 e.printStackTrace(); try { // conn.rollback(); // 一次性全部回滚 conn.rollback(sp1); conn.commit(); // 这次提交的就是回滚点之前的那个插入操作 } catch (SQLException throwables) { throwables.printStackTrace(); } } finally { // 5. 关闭连接 try { if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } } - 用到事务的地方很多,比如转账,一个减钱,一个加钱
- 批量更新数据库数据,我们在预通道方式下测试,相比通道就是多了一个传参的操作,不是直接传SQL到addBatch
public static void main(String[] args) { Connection conn = null; PreparedStatement pstmt = null; Savepoint sp1 = null; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 反射获取驱动类 String username = "root"; String password = "123456"; String url = "jdbc:mysql://localhost:3306/flask?serverTimezone=UTC"; // 2. 获取连接 conn = DriverManager.getConnection(url, username, password); conn.setAutoCommit(false); // 不自动提交 // 3. 定义SQL并执行 String sql_a = "insert into teacher(tname) values(?)"; pstmt = conn.prepareStatement(sql_a); // 批处理--------------- pstmt.setString(1, "Roy"); pstmt.addBatch(); pstmt.setString(1, "Allen"); pstmt.addBatch(); pstmt.setString(1, "Andy"); pstmt.addBatch(); int[] result = pstmt.executeBatch(); // 返回每条语句影响的行数 // 设置一个保存点 sp1 = conn.setSavepoint("sp1"); // 手动提交事务 conn.commit(); // 4. 取结果 for (int i:result) { System.out.println(i); } } catch (Exception e) { // 注意这里要用父类Exception,不然不走这块的的异常 e.printStackTrace(); try { conn.rollback(sp1); conn.commit(); // 这次提交的就是回滚点之前的那个插入操作 } catch (SQLException throwables) { throwables.printStackTrace(); } } finally { // 5. 关闭连接 try { if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); // 倒序关闭 } } catch (SQLException throwables) { throwables.printStackTrace(); } } } - 可以通过类反射执行上述所有的SQL操作,了解即可
- 为了避免每次写一堆重复步骤,封装一个工具类,包含连接库、增删改查的方法
import java.sql.*; import java.util.List; public class DBUtil { //1.定义需要的工具类对象 protected Connection connection=null; protected PreparedStatement pps=null; protected ResultSet rs=null; protected int k=0;//受影响的行数 private String url="jdbc:mysql://localhost:3306/flask"; private String username="root"; private String password="123456"; //2.加载驱动 static{ try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } //3.获得连接 protected Connection getConnection(){ try { connection=DriverManager.getConnection(url,username,password); } catch (SQLException e) { e.printStackTrace(); } return connection; } //4.创建通道 protected PreparedStatement getPps(String sql){ try { getConnection();//insert into users values(?,?,?,?,) pps=connection.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } return pps; } //5.给占位符赋值 list中保存的是给占位符所赋的值 private void setParams(List list){ try { if(list!=null&&list.size()>0){ for (int i=0;i - 在dao层新增接口,并实现
@Override public Student getByStuId(int id) { Student student = new Student(); String sql = "select * from student where stuid=?"; List list = new ArrayList(); list.add(id); // 预参数集合 ResultSet result = query(sql, list); try { if (result.next()) { student.setStuid(result.getInt("stuid")); student.setStuname(result.getString("stuname")); } return student; } catch (Exception e) { e.printStackTrace(); } finally { closeAll(); } return null; } // test.java public static void main(String[] args) { TeacherDao daot = new TeacherDaoImpl(); Student student = daot.getByStuId(1); // 单表查询 System.out.println(student.getStuname()); } - 一般在开发过程中,将固定在工具类中的字符串,放在属性文件中
- 名称必须是xxx.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/flask?serverTimezone=UTC user=root password=123456
// 修改DBUtil private static String url; private static String username; private static String password; private static String driverName; //2.加载驱动 static{ // 有两种方式 try { InputStream inputStream = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties"); Properties properties = new Properties(); properties.load(inputStream); driverName = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("user"); password = properties.getProperty("password"); Class.forName(driverName); // 第二种方式 ResourceBundle bundle = ResourceBundle.getBundle("db"); driverName = bundle.getString("driver"); url = bundle.getString("url"); username = bundle.getString("user"); password = bundle.getString("password"); Class.forName(driverName); } catch (ClassNotFoundException | IOException e) { e.printStackTrace(); } }
- 将数据库连接作为对象存储在内存中,请求数据库时,从连接池中取出一个已建立的空闲连接对象
- 自定义连接池
- 需要定义指定的属性和方法
- 属性: 集合,放置连接
- 方法: 获取连接方法、回收连接方法
public class Pool{ static linkedListlist = new linkedList (); static{ for (int i = 0; i < 10; i++) { // 在池子里初始化十个连接 Connection connection = JDBCUtils.newInstance().getConnection(); list.add(connection); } } public static Connection getConnection(){ if (list.isEmpty()) { //JDBCUtils类是自定义类,封装了连接数据库的信息代码 Connection connection = JDBCUtils.newInstance().getConnection(); list.addLast(connection); } Connection conn = list.removeFirst(); return conn; } public static void addBack(Connection conn){ if (list.size() >= 10) { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ list.addLast(conn); //10 } } public static int getSize(){ return list.size(); } } - Java也定义了接口(规范),让我们实现连接池,具体实现代码如下
- DataSource 接口有一个弊端,没有提供回收链接的方法,先装饰一下
public class MyConnection implements Connection { //将被装饰者导入 private Connection conn; private linkedListlist; public MyConnection(Connection conn, linkedList list) { super(); this.conn = conn; this.list = list; } @Override public T unwrap(Class iface) throws SQLException { return conn.unwrap(iface); } @Override public boolean isWrapperFor(Class> iface) throws SQLException { return conn.isWrapperFor(iface); } @Override public Statement createStatement() throws SQLException { return conn.createStatement(); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } @Override public CallableStatement prepareCall(String sql) throws SQLException { return null; } @Override public String nativeSQL(String sql) throws SQLException { return null; } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { } @Override public boolean getAutoCommit() throws SQLException { return false; } @Override 基于规范实现的连接池 public void commit() throws SQLException { conn.commit(); } @Override public void rollback() throws SQLException { conn.rollback(); } @Override public void close() throws SQLException { list.addLast(conn); } ... } // 实现 DataSource 连接池 public class DataSourcePool implements DataSource{ static linkedListlist = new linkedList (); static{ for (int i = 0; i < 10; i++) { Connection connection = JDBCUtils.newInstance().getConnection(); list.add(connection); } } public static int getSize(){ return list.size(); } @Override public Connection getConnection() throws SQLException { Connection conn = list.removeFirst(); MyConnection conn1 = new MyConnection(conn, list); return conn1; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public T unwrap(Class iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class> iface) throws SQLException { return false; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } } - 也就是说,不需要我们写连接池的代码,用上面的某种就可以
- 试一下DBCP的使用,将commons-dbcp-1.4.jar和commons-pool-1.5.6.jar导入进Modules
// 重写DBUtils public class DBUtil { //1.定义需要的工具类对象 protected Connection connection=null; protected PreparedStatement pps=null; protected ResultSet rs=null; protected int k=0;//受影响的行数 private static String url; private static String username; private static String password; private static String driverName; private static BasicDataSource basicDataSource = new BasicDataSource(); //2.加载驱动 static{ ResourceBundle bundle = ResourceBundle.getBundle("db"); driverName = bundle.getString("driver"); url = bundle.getString("url"); username = bundle.getString("user"); password = bundle.getString("password"); basicDataSource.setUsername(username); basicDataSource.setPassword(password); basicDataSource.setUrl(url); basicDataSource.setDriverClassName(driverName); basicDataSource.setInitialSize(15); } //3.获得连接 protected Connection getConnection(){ try { connection=basicDataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } //4.创建通道 protected PreparedStatement getPps(String sql){ try { getConnection();//insert into users values(?,?,?,?,) pps=connection.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } return pps; } //5.给占位符赋值 list中保存的是给占位符所赋的值 private void setParams(List list){ try { if(list!=null&&list.size()>0){ for (int i=0;i - dbcp没有自动回收空闲连接的功能,c3p0有自动回收空闲连接功能
- 在src下创建c3p0-config.xml,配置文件,名称固定;然后导入c3p0-0.9.1.2.jar
com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/flask?serverTimezone=UTC root 123456 30000 30 10 50 100 10 200 5 - 你没看错,db.properties 都不需要了
// 只需要改两行,不需要static静态代码块了 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); //3.获得连接 protected Connection getConnection(){ try { connection = comboPooledDataSource.getConnection(); // connection=basicDataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } - 目前,最流行的是阿里开发的德鲁伊,使用过程类似DBCP,需要db.properties
// druid private static DruidDataSource druidDataSource = new DruidDataSource();
- 学习目标
- 常见的开发模式:BS和CS
- 浏览器——服务器:通过浏览器访问网站
- 客户端——服务器:有客户端,不会实时更新代码
- Tomcat是目前最流行的Java服务器
- 服务器是部署在操作系统之上的,提供代码运行环境的容器
- 下载与安装
- 使用apache-tomcat-8.5.34,免安装,官网
- 要熟悉解压后每个文件夹的作用
- 启动:双击bin/startup.bat,如果闪退,可能是8080端口占用;也可能需要修改此文件
netstat -ano|findstr "8080" # 找到PID tasklist|findstr "PID" # 或者在startup.首行加一句 SET JAVA_HOME=D:Java
- 访问看是否启动:http://localhost:8080/
- 使用shutdown.bat可以关闭
- 在我们自己编写的项目目录`webapps``下
- 新建文件夹,相当于创建一个项目
- 新建HTML页面,测试访问
- IDEA整合Tomcat
- 可以大致参考:链接1,链接2
- 使用链接2,先直接创建普通Java项目
- 可以大致参考:链接1,链接2
- 点击启动,自动打开浏览器访问了:http://localhost:8080/demo_war_exploded/
- 可以在tomcat配置文件中修改端口号
- 路径太长,一般会在上图中的页面修改,设置Application context为 /
- 默认打成war包,名称就是Artifact的名称,也是项目名称
web工程: src: 存放java源代码 .java文件,下设不同package web: web资源 WEB-INF(受服务器保护,浏览器不能直接访问) lib-存放第三方jar包 web.xml 为整个web工程的配置部署描述文件,servlet listener filter session等配置信息 index.jsp 在浏览器中输入工程名时,默认访问的资源
- 增加管理员信息:conf/tomcat-users.xml,感觉没啥用



