当前位置:网站首页>JDBC courses
JDBC courses
2022-07-03 01:12:00 【__ Miracle__】
MVC
SELECT * from emp;
insert INTO emp VALUES(8888, 'ERIC', 'CLERK', 7902, '2000-01-12',40000 ,0, 40)
UPDATE EMP SET SAL = 70000 WHERE EMPNO = 8888;
DELETE FROM EMP WHERE EMPNO = 8888;
JDBC:
JDBC:Java DataBase Connectivity Java Database connection , Java Language operation database .JDBC The essence : Actually, it's official (sun company ) A set of rules defined to operate all relational databases , Interface . Each database manufacturer to implement this set of interface , Provide database driver jar package . We can use this interface (JDBC) Programming , The code that actually executes is the driver jar The implementation class in the package .
Quick start :
- step :
- Import driver jar package mysql-connector-java-5.1.37-bin.jar
1. Copy mysql-connector-java-5.1.37-bin.jar To the project libs Under the table of contents
2. Right click –>Add As Library - Registration drive
- Get database connection object Connection
- Definition sql
- Access to perform sql Object of statement Statement
- perform sql, Accept the return result
- Processing results
- Release resources
- Code implementation :
public class JDBCDemo1 {
public static void main(String[] args) throws Exception{
//1. Import driver jar package
//2. Registration drive
Class.forName("com.mysql.jdbc.Driver");
//3. Get database connection object
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/info", "root", "root");
//4. Definition sql sentence
String sql = "update account set balance = 500 where id = 1";
//5. Access to perform sql The object of Statement
Statement stmt = conn.createStatement();
//6. perform sql
int count = stmt.executeUpdate(sql);
//7. Processing results
System.out.println(count);
//8. Release resources
stmt.close();
conn.close();
}
}
Explain the objects in detail :
- DriverManager: Drive management objects
- function :
- Registration drive : Tell the program which database driver to use jar
static void registerDriver(Driver driver) : Register with given driver DriverManager .
Write code using : Class.forName(“com.mysql.jdbc.Driver”);
Discover by looking at the source code : stay com.mysql.jdbc.Driver Class has static code blocks
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}`
Be careful :mysql5 After the drive jar Packages can omit the steps of registering drivers .
2. Get database connection :
- Method :static Connection getConnection(String url, String user, String password)
- Parameters :
- url: Specify the path of the connection
- grammar :jdbc:mysql://ip Address ( domain name ): Port number / Database name
- Example :jdbc:mysql://localhost:3306/db3
- details : If the connection is local mysql The server , also mysql The default port for the service is 3306, be url I could just write it as :jdbc:mysql:/// Database name
- user: user name
- password: password
- url: Specify the path of the connection
- Connection: Database connection object
- function :
- Access to perform sql The object of
- Statement createStatement()
- PreparedStatement prepareStatement(String sql)
- Manage affairs :
- Open transaction :setAutoCommit(boolean autoCommit) : Call this method to set the parameter to false, That is, start the transaction
- Commit transaction :commit()
- Roll back the transaction :rollback()
- Statement: perform sql The object of
- perform sql
- boolean execute(String sql) : You can do whatever you want sql understand
- int executeUpdate(String sql) : perform DML(insert、update、delete) sentence 、DDL(create,alter、drop) sentence
- Return value : Number of rows affected , It can be judged by the number of rows affected DML Is the statement executed successfully Return value >0 Then the execution is successful , conversely , The failure .
- ResultSet executeQuery(String sql) : perform DQL(select) sentence
- practice :
- account surface Add a record
- account surface Modify the record
- account surface Delete a record
Code :
public class JDBCDemo2 {
public static void main(String[] args) {
Statement stmt = null;
Connection conn = null;
try {
//1. Registration drive
Class.forName("com.mysql.jdbc.Driver");
//2. Definition sql
String sql = "insert into account values(' Wang Wu ',null,3000)";
String sql = "update account set balance = 1 where id = 2";
String sql = "delete from account where id = 2";
//3. obtain Connection object
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/info", "root", "root");
//4. Access to perform sql The object of Statement
stmt = conn.createStatement();
//5. perform sql
int count = stmt.executeUpdate(sql);// Number of rows affected
//6. Processing results
System.out.println(count);
if(count > 0){
System.out.println(" Add success !");
}else{
System.out.println(" Add failure !");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//stmt.close();
//7. Release resources
// Avoid null pointer exceptions
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
- ResultSet: Result set object , Encapsulate query results
boolean next(): Move the cursor down one line , Determine whether the current line is the end of the last line ( Is there any data ), If it is , Then return to false, If not, return true
getXxx( Parameters ): get data
- Xxx: Representative data type Such as : int getInt() , String getString()
- Parameters :
- int: Represents the number of the column , from 1 Start Such as : getString(1)
- String: Name of representative column . Such as : getDouble(“balance”)
Be careful :
- Use steps :
- Move the cursor down one line
- Judge whether there is data
- get data
- Use steps :
// Loop to determine whether the cursor is at the end of the last line .
while(rs.next()){
// get data
//6.2 get data
int id = rs.getInt(1);
String name = rs.getString("name");
double balance = rs.getDouble(3);
System.out.println(id + "---" + name + "---" + balance);
}
- practice :
- Define a method , Inquire about emp The table's data encapsulates it as an object , Then load the collection , return .
Definition Emp class
Define methods public List findAll(){}
Implementation method select * from emp;
PreparedStatement: perform sql The object of
SQL Injection problem : In splicing sql when , Somewhat sql Special keywords participate in string splicing . Can cause security problems
- Input users casually , Input password :a’ or ‘a’ = 'a
- sql:select * from user where username = ‘fhdsjkf’ and password = ‘a’ or ‘a’ = ‘a’
solve sql Injection problem : Use PreparedStatement Object to solve
precompiled SQL: Parameters use ? As placeholder
step :
- Import driver jar package mysql-connector-java-5.1.37-bin.jar
- Registration drive
- Get database connection object Connection
- Definition sql
- Be careful :sql Parameter usage of ? As placeholder . Such as :select * from user where username = ? and password = ?;
- Access to perform sql Object of statement PreparedStatement Connection.prepareStatement(String sql)
- to ? assignment :
- Method : setXxx( Parameters 1, Parameters 2)
- Parameters 1:? Location number of from 1 Start
- Parameters 2:? Value
- Method : setXxx( Parameters 1, Parameters 2)
- perform sql, Accept the return result , There is no need to pass on sql sentence
- Processing results
- Release resources
Be careful : It will be used later PreparedStatement To complete all operations of addition, deletion, modification and query
- Can prevent SQL Inject
- More efficient
extract JDBC Tool class : JDBCUtils
- Purpose : Simplify writing
- analysis :
Registration drivers also extract
Extract a method to get the connection object
- demand : Don't want to pass parameters ( trouble ), We also need to ensure the universality of tool class .
- solve : The configuration file
jdbc.properties
url=
user=
password=
Extract a method to release resources
Druid: Database connection pool implementation technology , Provided by Alibaba
- step :
- Import jar package druid-1.0.9.jar
- Define configuration file :
- yes properties Formal
- You can call it anything , It can be placed in any directory
- Load profile .Properties
- Get database connection pool object : From the factory DruidDataSourceFactory
- Get the connection :getConnection
- Code :
//3. Load profile
Properties pro = new Properties();
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream(“druid.properties”);
pro.load(is);
//4. Get connection pool object
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
//5. Get the connection
Connection conn = ds.getConnection();
- Define tool classes
- Define a class JDBCUtils
- Provide static code block loading configuration file , Initialize connection pool object
- Provide methods
- Get the connection method : Get the connection through the database connection pool
- Release resources
- How to get the connection pool
- step :
- Code implementation :
package com.neusoft.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/** * Druid Connection pool tool class */
public class JDBCUtils {
//1. Define member variables DataSource
private static DataSource ds ;
static{
try {
//1. Load profile
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//2. obtain DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/** * Get the connection */
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/** * Release resources */
public static void close(Statement stmt,Connection conn){
/* if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close();// Return connection } catch (SQLException e) { e.printStackTrace(); } }*/
close(null,stmt,conn);
}
public static void close(ResultSet rs , Statement stmt, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();// Return connection
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/** * Get connection pool method */
public static DataSource getDataSource(){
return ds;
}
}
druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/info
username=root
password=root
# Initialize the number of connections
initialSize=5
# maximum connection
maxActive=10
# Maximum waiting time
maxWait=3000
JDBC Inquire about
public class JDBCDemo6 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1. Registration drive
Class.forName("com.mysql.jdbc.Driver");
//2. Get the connection object
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/info", "root", "root");
//3. Definition sql
String sql = "select * from account";
//4. Access to perform sql object
stmt = conn.createStatement();
//5. perform sql
rs = stmt.executeQuery(sql);
//6. Processing results
//6.1 Move the cursor down one line
rs.next();
//6.2 get data
int id = rs.getInt(2);
String name = rs.getString("name");
double balance = rs.getDouble(3);
System.out.println(id + "---" + name + "---" + balance);
//6.1 Move the cursor down one line
rs.next();
//6.2 get data
int id2 = rs.getInt(2);
String name2 = rs.getString("name");
double balance2 = rs.getDouble(3);
System.out.println(id2 + "---" + name2 + "---" + balance2);
//6.1 Move the cursor down one line
rs.next();
//6.2 get data
int id3 = rs.getInt(2);
String name3 = rs.getString("name");
double balance3 = rs.getDouble(3);
System.out.println(id3 + "---" + name3 + "---" + balance3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//7. Release resources
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
Rewrite to cycle
package com.neusoft.jdbc1;
import java.sql.*;
public class JDBCDemo7 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1. Registration drive
Class.forName("com.mysql.jdbc.Driver");
//2. Get the connection object
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/info", "root", "root");
//3. Definition sql
String sql = "select * from account";
//4. Access to perform sql object
stmt = conn.createStatement();
//5. perform sql
rs = stmt.executeQuery(sql);
//6. Processing results
// Loop to determine whether the cursor is at the end of the last line .
while(rs.next()){
// get data
//6.2 get data
int id = rs.getInt(2);
String name = rs.getString("name");
double balance = rs.getDouble(3);
System.out.println(id + "---" + name + "---" + balance);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//7. Release resources
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
Use common methods and connection pools to query emp
domain It usually represents the relationship with database tables – One to one correspondence javaBean
private Integer id;
private String ename;
private String job;
private Integer mgr;
private Date hiredate;
private int salary;
private int bonus;
private Integer deptno;
package com.neusoft.jdbc1;
import com.neusoft.domain.Emp;
import com.neusoft.utils.JDBCUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/** * * Define a method , Inquire about emp The table's data encapsulates it as an object , Then load the collection , return . */
public class JDBCDemo8 {
public static void main(String[] args) {
// List<Emp> list = new JDBCDemo8().findAll();
List<Emp> list = new JDBCDemo8().findAll2();
System.out.println(list);
System.out.println(list.size());
}
/** * Query all emp object * @return */
public List<Emp> findAll(){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Emp> list = null;
try {
//1. Registration drive
Class.forName("com.mysql.jdbc.Driver");
//2. Get the connection
conn = DriverManager.getConnection("jdbc:mysql:///info", "root", "root");
//3. Definition sql
String sql = "select * from emp";
//4. Access to perform sql The object of
stmt = conn.createStatement();
//5. perform sql
rs = stmt.executeQuery(sql);
//6. Traversal result set , Encapsulated object , Load collection
Emp emp = null;
list = new ArrayList<Emp>();
while(rs.next()){
// get data
int id = rs.getInt("empno");
String ename = rs.getString("ename");
String job = rs.getString("job");
int mgr = rs.getInt("mgr");
Date hiredate = rs.getDate("HIREDATE");
int salary = rs.getInt("sal");
int bonus = rs.getInt("comm");
int deptno = rs.getInt("deptno");
// establish emp object , And the assignment
emp = new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob(job);
emp.setMgr(mgr);
emp.setHiredate(hiredate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDeptno(deptno);
// Load collection
list.add(emp);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return list;
}
/** * demonstration JDBC Tool class * @return */
public List<Emp> findAll2(){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Emp> list = null;
try {
conn = JDBCUtils.getConnection();
//3. Definition sql
String sql = "select * from emp";
//4. Access to perform sql The object of
stmt = conn.createStatement();
//5. perform sql
rs = stmt.executeQuery(sql);
//6. Traversal result set , Encapsulated object , Load collection
Emp emp = null;
list = new ArrayList<Emp>();
while(rs.next()){
int id = rs.getInt("empno");
String ename = rs.getString("ename");
String job = rs.getString("job");
int mgr = rs.getInt("mgr");
Date hiredate = rs.getDate("HIREDATE");
int salary = rs.getInt("sal");
int bonus = rs.getInt("comm");
int deptno = rs.getInt("deptno");
// establish emp object , And the assignment
emp = new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob(job);
emp.setMgr(mgr);
emp.setHiredate(hiredate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDeptno(deptno);
// Load collection
list.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,stmt,conn);
}
return list;
}
}
SQL Inject
SQL Injection is one of the most common network attacks , It's not using the operating system BUG To achieve an attack , It's about the negligence of programmers , adopt SQL sentence , Login without account , Even tampering with the database .
select * from user where username = 'OR 1=1 -- and password= ';
This condition is bound to succeed
select * from user where username = ' ; Drop Database java9 -- and password= ';
How to solve sql Inject
- practice :
- demand :
- Enter the user name and password through the keyboard
- Determine whether the user has successfully logged in
- select * from user where username = “” and password = “”;
- If this sql There are query results , The successful , conversely , The failure
- step :
- Create database tables user
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
PASSWORD VARCHAR(32)
);
INSERT INTO USER VALUES(NULL,‘zhangsan’,‘123’);
INSERT INTO USER VALUES(NULL,‘lisi’,‘234’);
package com.neusoft.jdbc1;
import com.neusoft.utils.JDBCUtils;
import java.sql.*;
import java.util.Scanner;
/** * practice : * * demand : * 1. Enter the user name and password through the keyboard * 2. Determine whether the user has successfully logged in */
public class JDBCDemo9 {
public static void main(String[] args) {
//1. Keyboard Entry , Accept user name and password
Scanner sc = new Scanner(System.in);
System.out.println(" Please enter a user name :");
String username = sc.nextLine();
System.out.println(" Please input a password :");
String password = sc.nextLine();
//2. Calling method
boolean flag = new JDBCDemo9().login2(username, password);
//3. Judge the result , Output different statements
if(flag){
// Login successful
System.out.println(" Login successful !");
}else{
System.out.println(" Wrong user name or password !");
}
}
/** * Login method */
public boolean login(String username ,String password){
if(username == null || password == null){
return false;
}
// Connect the database to determine whether the login is successful
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
//1. Get the connection
try {
conn = JDBCUtils.getConnection();
//2. Definition sql
String sql = "select * from user where username = '"+username+"' and password = '"+password+"' ";
System.out.println(sql);
//3. Access to perform sql The object of
stmt = conn.createStatement();
//4. Execute the query
rs = stmt.executeQuery(sql);
//5. Judge
/* if(rs.next()){// If there's a next line , Then return to true return true; }else{ return false; }*/
return rs.next();// If there's a next line , Then return to true
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,stmt,conn);
}
return false;
}
/** * Login method , Use PreparedStatement Realization */
public boolean login2(String username ,String password){
if(username == null || password == null){
return false;
}
// Connect the database to determine whether the login is successful
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
//1. Get the connection
try {
conn = JDBCUtils.getConnection();
//2. Definition sql
String sql = "select * from user where username = ? and password = ?";
//3. Access to perform sql The object of
pstmt = conn.prepareStatement(sql);
// to ? assignment
pstmt.setString(1,username);
pstmt.setString(2,password);
//4. Execute the query , There is no need to pass on sql
rs = pstmt.executeQuery();
//5. Judge
/* if(rs.next()){// If there's a next line , Then return to true return true; }else{ return false; }*/
return rs.next();// If there's a next line , Then return to true
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,pstmt,conn);
}
return false;
}
}
JDBC Control the transaction :
- Business : A business operation with multiple steps . If the business operation is managed by a transaction , Then these multiple steps will either succeed at the same time , Or fail at the same time .
- operation :
- Open transaction
- Commit transaction
- Roll back the transaction
- Use Connection Object to manage affairs
- Open transaction :setAutoCommit(boolean autoCommit) : Call this method to set the parameter to false, That is, start the transaction
- In execution sql Before opening the transaction
- Commit transaction :commit()
- When all sql All commit transactions are completed
- Roll back the transaction :rollback()
- stay catch Rollback transaction
package com.neusoft.jdbc1;
import com.neusoft.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* The transaction operations
*/
public class JDBCDemo10 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//1. Get the connection
conn = JDBCUtils.getConnection();
// Open transaction
conn.setAutoCommit(false);
//2. Definition sql
//2.1 Zhang San - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 Li Si + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3. Access to perform sql object
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. Set parameters
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,3);
//5. perform sql
pstmt1.executeUpdate();
// Manual manufacturing abnormality
int i = 3/0;
pstmt2.executeUpdate();
// Commit transaction
conn.commit();
} catch (Exception e) {
// Transaction rollback
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,conn);
}
}
}
边栏推荐
- MySQL foundation 05 DML language
- matlab将数字矩阵保存为地理空间数据出错,显示下标索引必须为正整数类型或逻辑类型,解决
- matlab查找某一行或者某一列在矩阵中的位置
- Daily topic: movement of haystack
- Merge K sorted linked lists
- [case sharing] let the development of education in the new era advance with "number"
- R language ggplot2 visual faceting, visual facet_wrap bar plot, using strip Text function customize the size of the strip of each facet title in the facet graph (cutimi
- [AUTOSAR II appl overview]
- Machine learning terminology
- 18_ The wechat video number of wechat applet scrolls and automatically plays the video effect to achieve 2.0
猜你喜欢
[AUTOSAR five methodology]
2022.2.14 resumption
MySQL basic usage 02
(C语言)数据的存储
Esp32 simple speed message test of ros2 (limit frequency)
【FH-GFSK】FH-GFSK信号分析与盲解调研究
1696C. Fishingprince Plays With Array【思维题 + 中间状态 + 优化存储】
MySQL foundation 04 MySQL architecture
leetcode:701. Insertion in binary search tree [BST insertion]
Data analysis, thinking, law breaking and professional knowledge -- analysis method (I)
随机推荐
基于ARM RK3568的红外热成像体温检测系统
链表中的节点每k个一组翻转
SwiftUI 组件大全之使用 SceneKit 和 SwiftUI 构建交互式 3D 饼图(教程含源码)
JS inheritance and prototype chain
FPGA - 7 Series FPGA internal structure clocking -04- multi area clock
递归处理组织的几种情况
RK3568开发板评测篇(二):开发环境搭建
[AUTOSAR II appl overview]
Machine learning terminology
寻找标杆战友 | 百万级实时数据平台,终身免费使用
2022.2.14 resumption
Find a benchmark comrade in arms | a million level real-time data platform, which can be used for free for life
Explain the basic concepts and five attributes of RDD in detail
【C语言】分支和循环语句(上)
Win10 can't be installed in many ways Problems with NET3.5
数据分析思维分析犯法和业务知识——分析方法(一)
excel IF公式判断两列是否相同
Leetcode-2115: find all the dishes that can be made from the given raw materials
Daily topic: movement of haystack
按键精灵打怪学习-回城买药加血