MVC即model(模型)-view(视图)-controller(控制器)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑汇集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不须要从新编写业务逻辑。 html
耦合性低前端
视图层和业务层分离,这样就容许更改视图层代码而不用从新编译模型和控制器代码,一样,一个应用的业务流程或者业务规则的改变只须要改动MVC的模型层便可。由于模型与控制器和视图相分离,因此很容易改变应用程序的数据层和业务规则。java
模型是自包含的,而且与控制器和视图相分离,因此很容易改变应用程序的数据层和业务规则。若是把数据库从MySQL移植到Oracle,或者改变基于RDBMS数据源到LDAP,只需改变模型便可。一旦正确的实现了模型,无论数据来自数据库或是LDAP服务器,视图将会正确的显示它们。因为运用MVC的应用程序的三个部件是相互独立,改变其中一个不会影响其它两个,因此依据这种设计思想能构造良好的松耦合的构件
python
重用性高mysql
随着技术的不断进步,须要用愈来愈多的方式来访问应用程序。MVC模式容许使用各类不一样样式的视图来访问同一个服务器端的代码,由于多个视图能共享一个模型,它包括任何WEB(HTTP)浏览器或者无线浏览器(wap),好比,用户能够经过电脑也可经过手机来订购某样产品,虽然订购的方式不同,但处理订购产品的方式是同样的。因为模型返回的数据没有进行格式化,因此一样的构件能被不一样的界面使用。例如,不少数据可能用HTML来表示,可是也有可能用WAP来表示,而这些表示所须要的命令是改变视图层的实现方式,而控制层和模型层无需作任何改变。因为已经将数据和业务规则从表示层分开,因此能够最大化的重用代码了。模型也有状态管理和数据持久性处理的功能,例如,基于会话的购物车和电子商务过程也能被Flash网站或者无线联网的应用程序所重用。
jquery
生命周期成本低android
MVC使开发和维护用户接口的技术含量下降。git
部署快程序员
使用MVC模式使开发时间获得至关大的缩减,它使程序员(Java开发人员)集中精力于业务逻辑,界面程序员(HTML和JSP开发人员)集中精力于表现形式上。github
可维护性高
分离视图层和业务逻辑层也使得WEB应用更易于维护和修改。
有利软件工程化管理
因为不一样的层各司其职,每一层不一样的应用具备某些相同的特征,有利于经过工程化、工具化管理程序代码。控制器也提供了一个好处,就是可使用控制器来联接不一样的模型和视图去完成用户的需求,这样控制器能够为构造应用程序提供强有力的手段。给定一些可重用的模型和视图,控制器能够根据用户的需求选择模型进行处理,而后选择视图将处理结果显示给用户。
没有明确的定义
彻底理解MVC并非很容易。使用MVC须要精心的计划,因为它的内部原理比较复杂,因此须要花费一些时间去思考。同时因为模型和视图要严格的分离,这样也给调试应用程序带来了必定的困难。每一个构件在使用以前都须要通过完全的测试。
不适合小型,中等规模的应用程序
花费大量时间将MVC应用到规模并非很大的应用程序一般会得不偿失。
增长系统结构和实现的复杂性
对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增长结构的复杂性,并可能产生过多的更新操做,下降运行效率。
视图与控制器间的过于紧密的链接
视图与控制器是相互分离,但倒是联系紧密的部件,视图没有控制器的存在,其应用是颇有限的,反之亦然,这样就妨碍了他们的独立重用。
视图对模型数据的低效率访问
依据模型操做接口的不一样,视图可能须要屡次调用才能得到足够的显示数据。对未变化数据的没必要要的频繁访问,也将损害操做性能。
通常高级的界面工具或构造器不支持模式
改造这些工具以适应MVC须要和创建分离的部件的代价是很高的,会形成MVC使用的困难。
https://baike.baidu.com/item/MVC%E6%A1%86%E6%9E%B6/9241230?fr=aladdin&fromid=85990&fromtitle=MVC
GET请求的数据会附在URL以后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。若是数据是英文字母/数字,原样发送,若是是空格,转换为+,若是是中文/其余字符,则直接把字符串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII.
POST请求通常是表单提交的请求,他具备必定的数据安全性,不会再地址栏中显示提交数据,而且对编码没有要求.
注意:GET请求不得对数据进行增删改查,敏感数据要用POST方式提交
1 @Override 2 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 3 System.out.println("进入GET请求"); 4 String strG = req.getQueryString(); 5 System.out.println(strG); 6 }
若是URL传递中文后台获得的是相似%E4%BD%A0%E5的数据
1 @Override 2 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 3 System.out.println("进入POST请求"); 4 req.setCharacterEncoding("utf-8"); // 设置中文编码 5 String strP = req.getParameter("strP"); 6 System.out.println("strP = " + strP); 7 }
req.getParameter("str")方法中name要和须要提取数据的<input>标签中name="str"属性同样,而且返回String类型
若是参数值是文中就要使用req.setCharaEncoding("UTF-8")方法设置中文编码来防止乱码.
WEB-INF目录只有服务器能够访问到,未必避免用户经过URL直接访问系统页面,将后台页面放置在WEB-INF目录下.
1 @Override 2 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 3 System.out.println("进入POST请求"); 4 req.setCharacterEncoding("utf-8"); // 设置中文编码 5 String strP = req.getParameter("strP"); 6 System.out.println("strP = " + strP); 7 8 // 经过转发的方式跳转页面,若是刷新会重复以前的操做 9 req.getRequestDispatcher("/WEB-INF/jsp/Jsp.jsp").forward(req, resp); 10 }
经过req.getRequsetDispatcher("路径").forward(req.resp).
刷新转发后的页面,会重复以前的操做.转发后的URL地址是隐藏的因此它的安全性很好,且速度较快
1 @Override 2 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 3 System.out.println("进入GET请求"); 4 String strG = req.getQueryString(); 5 System.out.println(strG); 6 resp.sendRedirect("http://www.runoob.com/python/python-for-loop.html"); 7 }
重定向因为是经过servlet1重定向到servlet2,servlet2对服务器的请求响应作出响应.因此速度较慢,且URL地址会显示在地址栏.
重定向没法直接访问WEB-INF目录下的文件,但能够由Servlet2转发访问WEB-INF目录下的文件
1 @Override 2 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 3 System.out.println("进入GET请求"); 4 String strG = req.getQueryString(); 5 System.out.println(strG); 6 resp.sendRedirect("Servlet2"); 7 }
1 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException ,IOException { 2 System.out.println("进入Servler2程序的GET请求"); 3 // 网页转发到WEB-INF后台页面 4 req.getRequestDispatcher("/WEB-INF/jsp/Jsp.jsp").forward(req,resp); 5 } 6
JDBC(Java DataBase Connectivity,java数据库链接)是一种用于执行SQL语句的Java API,能够为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此能够构建更高级的工具和接口,使数据库开发人员可以编写数据库应用程序,同时,JDBC也是个商标名。
https://baike.baidu.com/item/jdbc/485214
下载相应数据库的连接文件,把jar文件放置在WEB-INF/lib目录下
1 import java.sql.Connection; 2 import java.sql.DatabaseMetaData; 3 import java.sql.DriverManager; 4 import java.sql.SQLException; 5 6 import org.junit.Test; 7 8 public class UserDao { 9 10 // 驱动信息 11 String username = "root"; 12 String password = "123456"; 13 String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false"; 14 String driver = "com.mysql.cj.jdbc.Driver"; 15 16 17 // 加载驱动 18 { 19 try { 20 Class.forName(driver); 21 } catch (ClassNotFoundException e) { 22 // TODO Auto-generated catch block 23 e.printStackTrace(); 24 } 25 } 26 27 // 查看链接数据库名称和版本号 28 @Test 29 public void m01() throws SQLException { 30 Connection conn = DriverManager.getConnection(url,username,password); 31 DatabaseMetaData dmd = conn.getMetaData(); 32 String name = dmd.getDatabaseProductName(); 33 String version = dmd.getDatabaseProductVersion(); 34 System.out.println(name + " " + version); 35 } 36 37 }
-MySQL 警告WARN: Establishing SSL connection without server's identity verification is not recommended.
首先恭喜,出现这个的时候MySQL说明已经安装成功了,这是警告不是错误,之后使用是不影响的。大概的意思就是说创建ssl链接,可是服务器没有身份认证,这种方式不推荐使用。
解决办法:
原来的链接url:Connection connect = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "letmein");
如今的链接url:Connection connect = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false","root", "letmein");
-java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized...
这是在使用MySQL 8.0以上版本(MySQL链接驱动和版本都是8.0以上)的时候出现的问题错误,咱们须要在访问数据库的Url后面加上如下的语句便可:
?serverTimezone=GMT%2B8
好比,我原来的url是:
"jdbc:mysql://localhost:3306/shiro_test"
应该修改成:
"jdbc:mysql://localhost:3306/shiro_test?serverTimezone=GMT%2B8"
从错误上看应该是时区的错误,因此咱们只须要设置完毕系统的时区便可。这里的GMT%2B8
表明东八区。
还有一种解决办法就是设置整个数据库的时区,能够执行下面的语句来完成:
show variables like '%time_zone%'
set global time_zone='+8:00';
参考:
https://blog.csdn.net/qq_15581405/article/details/52403576?locationNum=13
https://blog.csdn.net/github_35186068/article/details/80919528
1 // 添加 2 @Test 3 public void m02() throws SQLException{ 4 Connection conn = DriverManager.getConnection(url,username,password); 5 String sql = "insert into user1 values(?,?)"; 6 PreparedStatement ps = conn.prepareStatement(sql); // 对即将执行的SQL语句进行预编译,安全快速 7 ps.setInt(1,1); // 替换sql中第一个?为1 8 ps.setString(2,"小明"); // 替换sql中第二个?为"小明" 9 int i = ps.executeUpdate(); 10 conn.close(); // 关闭链接,链接占用很大资源 11 System.out.println( "i == " + i); 12 }
1 // 删除 2 @Test 3 public void m03() throws SQLException{ 4 Connection conn = DriverManager.getConnection(url,username,password); 5 String sql = "delete from user1 where user_id = ?"; 6 PreparedStatement ps = conn.prepareStatement(sql); 7 ps.setInt(1, 1); 8 int i = ps.executeUpdate(); 9 conn.close(); 10 System.out.println("i == " + i); 11 }
1 // 更改 2 @Test 3 public void m05() throws SQLException{ 4 Connection conn = DriverManager.getConnection(url,username,password); 5 String sql = "update user1 set user_name = '鑫鑫' where user_id = 3"; 6 PreparedStatement ps = conn.prepareStatement(sql); 7 int i = ps.executeUpdate(); 8 conn.close(); 9 System.out.println(i); 10 }
1 /** 2 * 查询 3 * Java数据类型 VS 数据库数据类型 4 * int VS int 5 * int VS decimal 6 * String VS varchar 7 * Date VS timestamp 8 * Date VS datetime 9 * @throws SQLException 10 */ 11 @Test 12 public void m04() throws SQLException{ 13 Connection conn = DriverManager.getConnection(url,username,password); 14 String sql = "select * from user1 "; 15 PreparedStatement ps = conn.prepareStatement(sql); 16 ResultSet rs = ps.executeQuery(); 17 while(rs.next()) { // 循环往下移动,同时rs.next()返回布尔类型,只有布尔类型才能用到循环条件里面 18 int user_id = rs.getInt(1); 19 String user_name = rs.getString("user_name"); 20 System.out.println(user_id + " " + user_name); 21 } 22 conn.close(); 23 }
sql语句最好使用preparedStatement(sql)方法预编译,这样作能够提升速度,防止sql注入提升安全性.
数据库链接占用很大资源,使用完就要关闭链接.
通常一个Java类对应model中的一个实体,model只关心数据模型,不关心数据获取。
1 public class UserModel { 2 3 protected int user_id = -1; 4 protected String user_name = null; 5 protected String user_password = null; 6 7 protected int getUser_id() { 8 return user_id; 9 } 10 protected void setUser_id(int user_id) { 11 this.user_id = user_id; 12 } 13 protected String getUser_name() { 14 return user_name; 15 } 16 protected void setUser_name(String user_name) { 17 this.user_name = user_name; 18 } 19 protected String getUser_password() { 20 return user_password; 21 } 22 protected void setUser_password(String user_password) { 23 this.user_password = user_password; 24 } 25 26 27 public void prin(){ 28 System.out.println(user_id + " " + user_name + " " + user_password); 29 } 30 31 32 }
Model中一个成员表明表中的一个字段。
Model要有Get和Set接口。
1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.PreparedStatement; 4 import java.sql.ResultSet; 5 import java.sql.SQLException; 6 7 import org.junit.Test; 8 9 public class UserDao2 { 10 // 驱动信息 11 String username = "root"; 12 String password = "123456"; 13 String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false"; 14 String driver = "com.mysql.cj.jdbc.Driver"; 15 16 //加载驱动 17 { 18 try { 19 Class.forName(driver); 20 } catch (ClassNotFoundException e) { 21 // TODO Auto-generated catch block 22 e.printStackTrace(); 23 } 24 } 25 26 27 // 得到数据并将数据存储到UserModel中 28 @Test 29 public void m01() throws SQLException{ 30 UserModel user = new UserModel(); // 实例化一个UserModel类 31 Connection conn = DriverManager.getConnection(url,username,password); 32 String sql = "select * from user1 where user_id = 3"; 33 PreparedStatement ps = conn.prepareStatement(sql); 34 ResultSet rs = ps.executeQuery(); 35 while(rs.next()){ 36 user.setUser_id(rs.getInt(1)); 37 user.setUser_name(rs.getString(2)); 38 user.setUser_password(rs.getString(3)); 39 } 40 conn.close(); 41 user.prin(); 42 } 43 }
由于DAO层只关注数据访问,因此须要Model来提供数据存储。
若是须要多个相同的Model,请使用集合框架。
1 import java.io.IOException; 2 import java.io.PrintWriter; 3 import java.sql.SQLException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.annotation.WebServlet; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 import com.google.gson.Gson; 12 13 14 15 @WebServlet("/UserService") 16 public class UserService extends HttpServlet{ 17 18 UserDao2 ud = new UserDao2(); 19 20 @Override 21 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 22 System.out.println("进入GET方法"); 23 // 建立用户表模型 24 UserModel user = new UserModel(); 25 user.setUser_id(4); 26 user.setUser_name("小徐"); 27 user.setUser_password("123456"); 28 29 // 将user对象转换成JSON文本(数据) 30 Gson gson = new Gson(); 31 String json = gson.toJson(user); // 转换数据格式 32 33 // 使用HttpServletResponse接口完成数据的响应处理 34 // 关键点:中文编码处理,数据响应格式的处理,输出数据到前端界面 35 resp.setCharacterEncoding("utf-8"); 36 resp.setContentType("test/plain"); 37 PrintWriter out = resp.getWriter(); 38 out.println(json); 39 out.flush(); 40 out.close(); 41 } 42 43 @Override 44 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 45 // TODO Auto-generated method stub 46 super.doPost(req, resp); 47 } 48 49 50 }
Gson.toJson(Model)能够将Model转化成Json对象
resp.setCharacterEncoding("UTF-8")方法设置中文编码
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>MyHtml.html</title> 5 <meta charset="UTF-8"> 6 <script src="../jquery-1.11.0.min.js"></script> 7 <script> 8 $(function(){ 9 $("#btn").click(function(){ 10 $.ajax({ 11 url: "../UserService", 12 type: "GET", 13 success: function(data){ 14 console.log("发送成功"); 15 var div = document.getElementById("div"); 16 div.innerHTML += data.user_id + " " + data.user_name + " " + data.user_password; 17 }, 18 dataType: "json" 19 }); 20 }); 21 }); 22 </script> 23 </head> 24 <body> 25 This is my HTML page. 26 <br> 27 <a href="../servlet?strp=中文">链接</a> 28 <br> 29 <form action="../servlet" method="POST"> 30 <input type="text" name="strP"> <input type="submit"> 31 </form> 32 <button id="btn">点击</button> 33 <div id="div"></div> 34 </body> 35 </html>
1 import java.io.IOException; 2 import java.io.PrintWriter; 3 import java.sql.SQLException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.annotation.WebServlet; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 import com.google.gson.Gson; 12 13 14 15 @WebServlet("/UserService") 16 public class UserService extends HttpServlet{ 17 18 UserDao2 ud = new UserDao2(); 19 20 @Override 21 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 22 System.out.println("进入GET方法"); 23 // 建立用户表模型 24 UserModel user = new UserModel(); 25 user.setUser_id(4); 26 user.setUser_name("小徐"); 27 user.setUser_password("123456"); 28 29 // 将user对象转换成JSON文本(数据) 30 Gson gson = new Gson(); 31 String json = gson.toJson(user); // 转换数据格式 32 33 // 使用HttpServletResponse接口完成数据的响应处理 34 // 关键点:中文编码处理,数据响应格式的处理,输出数据到前端界面 35 resp.setCharacterEncoding("utf-8"); 36 resp.setContentType("test/plain"); 37 PrintWriter out = resp.getWriter(); 38 out.println(json); 39 out.flush(); 40 out.close(); 41 } 42 43 44 }
url: Java服务程序地址
type: 请求方式
success: function(data){ } 回调函数
dataType: 返回的数据格式
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>MyHtml.html</title> 5 <meta charset="UTF-8"> 6 <script src="../jquery-1.11.0.min.js"></script> 7 <script> 8 $(function(){ 9 $("#btn").click(function(){ 10 user_id = document.getElementById("a1").innerHTML; 11 user_name = document.getElementById("a2").innerHTML; 12 $.ajax({ 13 url: "../UserService", 14 type: "post", 15 dataType: "json", 16 data: {"user_id":user_id , "user_name":user_name } 17 }); 18 }); 19 }); 20 </script> 21 </head> 22 <body> 23 This is my HTML page. 24 <br> 25 <a href="../servlet?strp=中文">链接</a> 26 <br> 27 <form action="../servlet" method="POST"> 28 <input type="text" name="strP"> <input type="submit"> 29 </form> 30 <button id="btn">点击</button> 31 <div id="div"></div> 32 <div id="a1">5</div> 33 <div id="a2">小红</div> 34 </body> 35 </html>
1 import java.io.IOException; 2 import java.io.PrintWriter; 3 import java.sql.SQLException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.annotation.WebServlet; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 import com.google.gson.Gson; 12 13 14 15 @WebServlet("/UserService") 16 public class UserService extends HttpServlet{ 17 18 19 @Override 20 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 21 // 设置中文编码 22 req.setCharacterEncoding("UTF-8"); 23 String user_id = req.getParameter("user_id"); 24 String user_name = req.getParameter("user_name"); 25 System.out.println(user_id + " " + user_name); 26 } 27 28 29 }
若是data:中传入dom对象会报 Maximum call stack size exceeded
Web程序中经常使用的技术,用来跟踪用户的整个会话。经常使用的会话跟踪技术是Cookie与Session。Cookie经过在客户端记录信息肯定用户身份,Session经过在服务器端记录信息肯定用户身份。
一次会话指的是:就比如打电话,A给B打电话,接通以后,会话开始,直到挂断电话,该次会话就结束了,而浏览器访问服务器,就跟打电话同样,浏览器A给服务器发送请求,访问web程序,该次会话就已经接通,其中无论浏览器发送多少请求(就至关于接通电话后说话同样),都视为一次会话,直到浏览器关闭,本次会话结束。其中注意,一个浏览器就至关于一部电话,若是使用火狐浏览器,访问服务器,就是一次会话了,而后打开google浏览器,访问服务器,这是另外一个会话,虽然是在同一台电脑,同一个用户在访问,可是,这是两次不一样的会话。
知道了什么是会话后,思考一个问题,一个浏览器访问一个服务器就能创建一个会话,若是别的电脑,都同时访问该服务器,就会建立不少会话,就拿一些购物网站来讲,咱们访问一个购物网站的服务器,会话就被建立了,而后就点击浏览商品,对感兴趣的商品就先加入购物车,等待一块儿付帐,这看起来是很普通的操做,可是想一下,若是有不少别的电脑上的浏览器同时也在访问该购物网站的服务器,跟咱们作相似的操做呢?服务器又是怎么记住用户,怎么知道用户A购买的任何商品都应该放在A的购物车内,不管是用户A什么时间购买的,不能放入用户B或用户C的购物车内的呢?因此就有了cookie和session这两个技术,就像第一行说的那样,cookie和session用来跟踪用户的整个会话,
假如一个咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就须要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:
一、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种作法就是协议自己支持状态。可是http协议自己是无状态的
二、发给顾客一张卡片,上面记录着消费的数量,通常还有个有效期限。每次消费时,若是顾客出示这张卡片,则这次消费就会与之前或之后的消费相联系起来。这种作法就是在客户端保持状态。也就是cookie。 顾客就至关于浏览器,cookie如何工做,下面会详细讲解
三、发给顾客一张会员卡,除了卡号以外什么信息也不纪录,每次消费时,若是顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种作法就是在服务器端保持状态。
因为HTTP协议是无状态的,而出于种种考虑也不但愿使之成为有状态的,所以,后面两种方案就成为现实的选择。具体来讲cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时咱们也看到,因为采用服务器端保持状态的方案在客户端也须要保存一个标识,因此session机制可能须要借助于cookie机制来达到保存标识的目的,但实际上它还有其余选择
原文:https://blog.csdn.net/android_bar/article/details/70570216
2018-9-4