Spark SQL(二十三)Spark SQL数据源

定义

Spark SQL可以通过DataFream接口操作各种数据源。可以通过关系转换或者临时表来操作DataFrame。这里我们将介绍通用的数据源加载方法和数据保存方法。

通用加载/保存方法

Spark默认的数据源格式为Parquet格式,数据源格式问Parquet文件的时候,Spark读取数据的时候不需要指定具体的格式,如果想要修改默认的数据格式,就需要修改spark.sql.sources.default参数

 def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName("DataFrameTest").master("local[2]").getOrCreate()
    //因为Parquet格式的文件时Spark加载数据的默认格式,所以不需要指定format格式
    val personDF: DataFrame = spark.read.load("hdfs://xxxxx:8020/testfile/person.parquet")
    personDF.show()
  }

如果我们输入的数据文件格式不是Parquet,那么我们就需要手动指定读取的数据源格式。数据源格式需要指定全名(org.apache.spark.sql.parquet),如果手动指定的数据源格式为spark内置格式,只需要指定简称,如json、parquet、jdbc、text、orc、libsvm、csv

 def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName("DataFrameTest").master("local[2]").getOrCreate()

    //从本地读取json格式的数据,因为json不是spark默认的数据源格式,所以需要手动指定数据源格式为json
    val personDF: DataFrame = spark.read.format("json").load("C:\\Users\\39402\\Desktop\\person.json")
    
    //利用Dataframe的write和save方法将本地读取到的数据以parquet的格式写到HDFS上,
    personDF.write.format("parquet").save("hdfs://xxxxx:8020/testfile/person.parquet")
  }

除此之外我们也可以把SQL执行在数据源文件上,下边就是一个例子,它将SQL运行在文件上。

def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName("DataFrameTest").master("local[2]").getOrCreate()
    
    //将SQL执行在数据源的文件上,一定要注意书写格式
 	spark.sql("select * from parquet.`hdfs://xxxxx:8020/testfile/person.parquet`").show()
  }

文件保存选项

当我们的SparkSQL处理完数据以后,需要向本地或者文件系统保存数据,我们可以利用SaveModel指定保存策略,,例如文件已经存在就抛异常、覆盖原数据等。
代码例子

def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName("DataFrameTest").master("local[2]").getOrCreate()

    //从本地读取json格式的数据,因为json不是spark默认的数据源格式,所以需要手动指定数据源格式为json
    val personDF: DataFrame = spark.read.format("json").load("C:\\Users\\39402\\Desktop\\person.json")
   
    //指定SaveModel策略为如果文件已存在就抛异常
	personDF.write.mode(SaveMode.ErrorIfExists).save("hdfs://xxxx:8020/testfile/person.parquet")
  }

SaveModel保存策略表

Scala/JavaMeaning
SaveMode.ErrorIfExists (default)如果数据已经存在,将DataFrame保存到数据源时,则预计会抛出异常。
SaveMode.Append如果data / table已经存在,将DataFrame保存到数据源时,则DataFrame的内容将被添加到现有数据中。
SaveMode.Overwrite覆盖模式意味着将DataFrame保存到数据源时,如果data / table已经存在,则现有数据将被DataFrame的内容覆盖。
SaveMode.Ignore忽略模式意味着,当将DataFrame保存到数据源时,如果数据已经存在,保存操作将不会保存DataFrame的内容,也不会更改现有数据。这与CREATE TABLE IF NOT EXISTSSQL中的类似。

Parquet文件

Parquet是一种列式存储格式,可以很高效的存储具有嵌套的数据,目前是最流行的一种存储格式。SparkSQL默认的读写数据的格式就是Parquet,可想而知该格式是多么高效。在海量数据中,我们一般存储在分布式文件系统中,并且以分区的方式存储,也就是按照一定的规律拆分成小的文件。那么Parquet数据源就可以自动发现并解析分区信息。SparkSQL自动解析分区的参数为spark.sql.sources.partitionColumnTypeInference=true,默认是开启,想要关闭就设置成disabled

Hive数据库

HiveHadoop为了减缓编写MapReduce程序而存在的一种SQL引擎,它最终会被翻译成MapReduce程序。SparkSQL支持操作现有的Hive仓库。由于Hive有很多的依赖,这些依赖不会包含在Spark中,当在Spark没有在ClassPath中发现这些依赖,那么Spark就会自动加载它们,特别注意的是,这些依赖一定要在所有节点上出现,因为它们需要访问Hive的序列化和反序列库,以便于访问Hive上的数据。所谓的依赖其实就是将HIVE_HOME/hive-site.xml、HADOOP_HOME/etc/hadoop/core-site.xml、HADOOP_HOME/etc/hadoop/hdfs-site.xml文件复制到Spark目录下的conf目录中。特别注意的是在编译Spark的时候必须添加Hive依赖。如果在Spark中运行有关Hive的操作,这个时候如果你的Hive环境还没有部署好,那么Spark会在当前的工作目录中创建Hive的元数据仓库,叫做metastore_db。利用SparkSQL运行Hive存储数据的默认目录是HDFS上的/user/hive/warehouse。以上环境准备好了,那么我们怎么样才能在Spark中操作Hive呢,请看下边的代码例子。

object HiveDataSourceTest {
  //Hive数据存储在HDFS上的目录
  val hiveDataPathUrl = "/user/hive/warehouse"

  def main(args: Array[String]): Unit = {

    //初始化SparkSession,并支持Hive操作,然后hive数据在hdfs上的存储目录。
    val spark: SparkSession = SparkSession.builder()
      .appName("HiveDataSourceTest")
      .master("local[2]") //在Spark集群上运行的时候要去掉
      .config("spark.sql.warehouse.dir", hiveDataPathUrl)
      .enableHiveSupport()
      .getOrCreate()

    //利用spark sql创建Hive表
    spark.sql("CREATE TABLE IF NOT EXISTS person(name:StRING,age:INT) ROW FORMAT DELIMITED " +
      "FIELDS  TERMINATED BY ',' ")

    //利用spark sql 将数据加载到Hive表中
    spark.sql("LOAD DATA LOCAL INPATH '/opt/data/person.json' INTO TABLE person ")

    //利用spark sql 查询Hive表
    spark.sql("select * from person").show()

    //关闭SparkSession
    spark.close()
  }
}

使用spark-shell操作外部Hive

当我们使用已存在的Hive仓库,需要操作其上的数据,那我们就需要将HIVE_HOME/conf下的hive-site.xml拷贝到SPARK_HOME/conf下,然后在启动spark-shell的时候,一定要加上--jars /xx/xx/mysql-connector-java-xx.xx.jar,因为Hive元数据存在Mysql中,Driver程序需要连接Mysql

spark-shell --jars /opt/lib/mysql-connector-java-6.0.1.jar

JSON数据库

SparkSQL能够自动推测出JSON数据集的结构,并将它加载成一个DataSet[Row],也就是一个DataFrame。可以通过spark.read.json()的方式去读取JSON文件或者JSON字符串,然后返回一个DataFrame数据集。

 def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName("DataFrameTest").master("local[2]").getOrCreate()
    //第一种读取本地或者文件系统上的json文件
    val personDF: DataFrame = spark.read.json("C:\\Users\\39402\\Desktop\\person.json")
    
      //导入隐式转换
    import spark.implicits._
  
    //第二种读取json字符串,
    val jsonDataSet: Dataset[String] = spark.createDataset("""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)

    //将DataSet转换成RDD,然后作为json方法的参数
    spark.read.json(jsonDataSet.rdd).show()
  }

JDBC

SparkSQL也包含了一种能够通过JDBC读取其他的数据源的数据源。这种方式比使用JdbcRDD的性能更高。这是因为这种方式的返回值是DataFrame,这样就可以利用SparkSQL进行处理,或者是跟其他数据源交互。JBDC这种方式还有的一种优点就是可以很容易利用Java或者Python操作。以下是SparkSQL利用JDBC方式操作Mysql数据库的代码。

package com.lyz.datasource

import java.util.Properties

import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}

object JDBCDataSourceTest {

  def main(args: Array[String]): Unit = {
    val spark: SparkSession = SparkSession.builder().appName("DataFrameTest").master("local[2]").getOrCreate()

    //SparkSQL利用JDBC读取Mysql数据第一种方式
    val table1: DataFrame = spark.read.format("jdbc")
      .option("url", "jdbc:mysql://localhost:3306/kettle_test?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&serverTimezone=GMT&useSSL=false")
      .option("dbtable", "local_table")
      .option("driver", "com.mysql.cj.jdbc.Driver")
      .option("user", "root")
      .option("password", "12345")
      .option("fetchsize", 5)
      .option("batchsize", 10).load()

    //SparkSQL利用JDBC读取Mysql数据第二种方式
    val connectionProperties: Properties = new Properties()
    connectionProperties.put("user", "root")
    connectionProperties.put("password", "12345")
    val table2: DataFrame = spark.read.jdbc("jdbc:mysql://localhost:3306/kettle_test?zeroDateTimeBehavior=convertToNull&characterEncoding" +
      "=utf8&serverTimezone=GMT&useSSL=false", "local_table", connectionProperties)

    //SparkSQL利用JDBC写数据到Mysql中的第一种方式
    table1.write.mode(SaveMode.Append).format("jdbc").option("url", "jdbc:mysql://localhost:3306/kettle_test?zeroDateTimeBehavior=convertToNull&characterEncoding=utf8&serverTimezone=GMT&useSSL=false")
      .option("dbtable", "remote_table")
      .option("driver", "com.mysql.cj.jdbc.Driver")
      .option("user", "root")
      .option("password", "12345")
      .option("fetchsize", 5)
      .option("batchsize", 10).save()


    //SparkSQL利用JDBC写数据到Mysql中的第二种方式

    table2.write.mode(SaveMode.Append).jdbc("jdbc:mysql://localhost:3306/kettle_test?zeroDateTimeBehavior=convertToNull&characterEncoding" +
      "=utf8&serverTimezone=GMT&useSSL=false", "remote_table", connectionProperties)
  }
}

MongDB

package com.lyz
import java.net.InetAddress
import com.mongodb.casbah.commons.MongoDBObject
import com.mongodb.casbah.{MongoClient, MongoClientURI}
import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest
import org.elasticsearch.common.settings.Settings
import org.elasticsearch.common.transport.InetSocketTransportAddress
import org.elasticsearch.transport.client.PreBuiltTransportClient

object dataloader {

  val MOVIC_PATH = "D:\\workspace-idea\\java\\recommend-system\\movie-recommend-system\\data-loader\\src\\main\\resources\\movies.csv"

  val MONGODB_MOVIE_COLLECTION = "Movie"
  
  //主函数,程序的入口
  def main(args: Array[String]): Unit = {

    val config = Map(
      "spark.cores" -> "local[*]",
      "mongodb.url" -> "mongodb://xxxx:27017/recommend",
      "mongodb.db" -> "recommend")

    //创建一个Spark配置对象
    val sparkConf = new SparkConf().setAppName("DataLoader").setMaster(config("spark.cores"))
    //创建一个SparkSession
    val spark: SparkSession = SparkSession.builder().config(sparkConf).getOrCreate()


    import spark.implicits._
    //读取Moive数据并转换成DataFrame
    val movieDF: DataFrame = spark.sparkContext.textFile(MOVIC_PATH).map(line => {
      val attr: Array[String] = line.split("\\^")
      Movie(attr(0).toInt, attr(1).trim, attr(2).trim, attr(3).trim, attr(4).trim, attr(5).trim, attr(6).trim, attr(7).trim, attr(8).trim, attr(9).trim)
    }).toDF()
    
    //声明一个隐式转换,
    implicit val mongoConfig = MongoConfig(config("mongodb.url"), config("mongodb.db"))
    saveDataIntoMongodb(movieDF, ratingDF, tagDF)
    spark.stop()
  }
  
  def saveDataIntoMongodb(movie: DataFrame, rating: DataFrame, tag: DataFrame)(implicit mongoConfig: MongoConfig): Unit = {
    val mongoClient = MongoClient(MongoClientURI(mongoConfig.uri))
    //判断MongoDB中是否存在了对应的数据库,如果存在就该删除它
    mongoClient(mongoConfig.db)(MONGODB_MOVIE_COLLECTION).dropCollection()
    
    //向MongoDB写Movie数据
    movie.write
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_MOVIE_COLLECTION)
      .mode(SaveMode.Overwrite)
      .format("com.mongodb.spark.sql")
      .save()

    //为mogondb里的表建立索引
    //为MongoDB中的Movie表创建索引,索引为mid,并且排序规则为升序
    mongoClient(mongoConfig.db)(MONGODB_MOVIE_COLLECTION).createIndex(MongoDBObject("mid" -> 1))
    //关闭mongoDB的客户端
    mongoClient.close()
  }
}
/**
  * MongoDB的连接配置
  *
  * @param uri MongoDB的连接
  * @param db  MongoDB要操作数据库
  */
case class MongoConfig(uri: String, db: String)

ElasticSearch

package com.lyz
import java.net.InetAddress

import com.mongodb.casbah.commons.MongoDBObject
import com.mongodb.casbah.{MongoClient, MongoClientURI}
import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest
import org.elasticsearch.common.settings.Settings
import org.elasticsearch.common.transport.InetSocketTransportAddress
import org.elasticsearch.transport.client.PreBuiltTransportClient

object dataloader {

  val MOVIC_PATH = "D:\\workspace-idea\\java\\recommend-system\\movie-recommend-system\\data-loader\\src\\main\\resources\\movies.csv"
  val ES_MOVIE_INDEX = "Movie"

  //主函数,程序的入口
  def main(args: Array[String]): Unit = {

    val config = Map(
      "spark.cores" -> "local[*]",
      "es.transportHosts" -> "xxxx:9300",
      "es.index" -> "recommend",
      "es.cluster.name" -> "es-cluster")

    //创建一个Spark配置对象
    val sparkConf = new SparkConf().setAppName("DataLoader").setMaster(config("spark.cores"))
    //创建一个SparkSession
    val spark: SparkSession = SparkSession.builder().config(sparkConf).getOrCreate()


    import spark.implicits._
    //读取Moive数据并转换成DataFrame
    val movieDF: DataFrame = spark.sparkContext.textFile(MOVIC_PATH).map(line => {
      val attr: Array[String] = line.split("\\^")
      Movie(attr(0).toInt, attr(1).trim, attr(2).trim, attr(3).trim, attr(4).trim, attr(5).trim, attr(6).trim, attr(7).trim, attr(8).trim, attr(9).trim)
    }).toDF()
    
    //声明一个ES配置的隐式参数
    implicit val eSConfig = ESConfig(
      config("es.httpHosts"),
      config("es.transportHosts"),
      config("es.index"),
      config("es.cluster.name"))
      
    saveDataIntoEs(movieDF)
    
    spark.stop()
  }

  def saveDataIntoEs(movie: DataFrame)(implicit eSConfig: ESConfig): Unit = {
    //新建一个配置
    val esSetting: Settings = Settings.builder().put("cluster.name", "es-cluster").build()
    //需要将TransportHosts添加到esClient中
    val REGEX_HOST_PORT = "(.+):(\\d+)".r
    val esClient = new PreBuiltTransportClient(esSetting)

    eSConfig.transportHosts.split(",").foreach {
      //模式匹配,忽略掉不合规的URL地址
      case REGEX_HOST_PORT(host: String, port: String) =>
        esClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port.toInt))
    }

    //判断es中是否还存在旧的数据索引
    if (esClient.admin().indices().exists(new IndicesExistsRequest(eSConfig.index)).actionGet().isExists) {
      //删除旧的索引数据
      esClient.admin().indices().delete(new DeleteIndexRequest(eSConfig.index))
    }

    //创建索引
    esClient.admin().indices().create(new CreateIndexRequest(eSConfig.index))


    movie.write.
      option("es.nodes", eSConfig.httpHosts)
      .option("es.http.timeout", "100m")
      .option("es.mapping.id", "mid")
      .mode(SaveMode.Overwrite)
      .format("org.elasticsearch.spark.sql")
      .save(eSConfig.index + "/" + ES_MOVIE_INDEX)

    esClient.close()
  }
}



case class Movie(mid: Int, name: String, descri: String, timelong: String, issue: String,
                 shoot: String, language: String, genres: String, actors: String, directors: String)

/**
  * ElasticSearch的连接配置
  *
  * @param httpHosts      Http的主机列表,以,分割
  * @param transportHosts Transport主机列表, 以,分割
  * @param index          需要操作的索引
  * @param clustername    ES集群的名称,
  */
case class ESConfig(httpHosts: String, transportHosts: String, index: String, clustername: String)

性能数据调优

SparkSQL作业中,我们可利用缓存数据来调优,将数据缓存在内存中,提高执行效率,我们可以利用spark.catalog.cacheTable("tableName")或者利用dataFrame.cache()方法来将数据缓存在内存中。删除缓存数据可以调用spark.catalog.uncacheTale("tableName")来删除缓存数据。我们可以根据实际情况来设置缓存大小等一下参数。spark.conf(key,value)来设置。下表是具体的参数

参数名称默认值具体的意思
spark.sql.inMemoryColumnarStorage.compressedtrue是否为每列选择压缩编解码器
spark.sql.inMemoryColumnarStorage.batchSize10000控制缓存批次大小
package com.bjsxt.servlet; import com.bjsxt.entity.User; import com.bjsxt.service.UserService; import com.bjsxt.service.impl.UserServiceImpl; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.*; import java.io.IOException; import java.net.URLEncoder; import java.sql.Date; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class UserServlet extends BaseServlet { // @Override // protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // //解决POST表单的中文乱码问题 // request.setCharacterEncoding("utf-8"); // //接收method属性的值 // String methodName = request.getParameter("method"); // // //根据method属性的值调用相应的方法 // if("login".equals(methodName)){ // this.login(request,response); // }else if("register".equals(methodName)){ // this.register(request,response); // }else if("logout".equals(methodName)){ // this.logout(request,response); // } // // } public void show(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取表单的数据 String userId = request.getParameter("userId"); if(userId == null){ userId = ""; } String strAge = request.getParameter("minAge"); int minAge = 0; try{ minAge = Integer.parseInt(strAge); //"12" "abc" }catch(NumberFormatException e){ e.printStackTrace(); } //调用业务层完成查询操作 UserService userService = new UserServiceImpl(); //List<User> userList = userService.findAll(); List<User> userList = userService.find(userId,minAge); //List<User> userList = null; //List<User> userList = new ArrayList<User>(); //跳转到show.jsp显示数据 request.setAttribute("userId",userId); request.setAttribute("minAge",strAge); request.setAttribute("ulist",userList); request.getRequestDispatcher("/admin/show.jsp").forward(request,response); } public void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //结束当前的session request.getSession().invalidate(); //跳转回登录页面 response.sendRedirect(request.getContextPath()+"/admin/login.jsp"); } public void register(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //request.setCharacterEncoding("utf-8"); //1.接收来自视图层的表单数据 String userId = request.getParameter("userId"); String realName = request.getParameter("realName"); String pwd = request.getParameter("pwd"); String rePwd = request.getParameter("repwd"); int age = Integer.parseInt(request.getParameter("age"));// "23" String [] hobbyArr = request.getParameterValues("hobby"); String strDate = request.getParameter("enterDate");//"1999-12-23" Date enterDate = Date.valueOf(strDate); //util.Date SimpleDateFormat //判断两次密码是否相同 if(pwd == null || !pwd.equals(rePwd)){ request.setAttribute("error","两次密码必须相同"); request.getRequestDispatcher("/admin/register.jsp").forward(request,response); return; } //2.调用业务层完成注册操作并返回结果 User user = new User(userId,realName,pwd,age, Arrays.toString(hobbyArr),enterDate); UserService userService = new UserServiceImpl(); int n = userService.register(user); //3.根据结果进行页面跳转 if(n>0){ response.sendRedirect(request.getContextPath()+"/admin/login.jsp"); }else{ request.setAttribute("error","注册失败"); request.getRequestDispatcher("/admin/register.jsp").forward(request,response); } } public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //解决POST表单的中文乱码问题 //request.setCharacterEncoding("utf-8"); //获取用户名和密码 request 内建对象 请求 String username = request.getParameter("username"); String password = request.getParameter("password"); String rememberme = request.getParameter("rememberme"); //调用下一层判断登录是否成功,并返回结果 //进行服务器端的表单验证 if(username ==null || "".equals(username)){ request.setAttribute("error","用户名不能为空JSP"); request.getRequestDispatcher("/admin/login.jsp").forward(request,response); return; } if (username.length()<=6){ request.setAttribute("error","用户名长度必须大于6JSP"); request.getRequestDispatcher("/admin/login.jsp").forward(request,response);//后面语句还会执行 return; //后面的语句不再执行 } // boolean flag = false;//默认失败 // if(username.indexOf("sxt")>=0 || username.contains("尚学堂")){ // flag = true; // } User user = null;//默认登录失败 // UserDao userDao = new UserDaoImpl(); // user = userDao.find(username,password); UserService userService = new UserServiceImpl(); user = userService.login(username,password); //userService.addOrder("shoppingCart"); //输出结果 if(user != null){ //登录成功才记住我 //1.办理会员卡 String username2 = URLEncoder.encode(username,"utf-8"); Cookie cookie1 = new Cookie("uname",username2); Cookie cookie2 = new Cookie("password",password); //2.指定会员卡的作用范围,默认范围是当前目录 /servlet/LoginServlet /admin/login.jsp //cookie1.setPath("/"); //当前服务器 cookie1.setPath("/myservlet2/"); //当前项目 cookie2.setPath("/myservlet2"); //3.指定会员卡的作用时间 if("yes".equals(rememberme)){ cookie1.setMaxAge(60*60*24*10); //默认的时间浏览器不关闭的时间;-1 表示一直有效 cookie2.setMaxAge(60*60*24*10); }else{ cookie1.setMaxAge(0); cookie2.setMaxAge(0); } //4.将会员卡带回家 response.addCookie(cookie1); response.addCookie(cookie2); //成功跳转到成功页面 //out.println("登录成功"); // /servlet/LoginServlet // /servlet/success.jsp // request.getRequestDispatcher("/admin/success.jsp").forward(request,response); HttpSession session = request.getSession(); // session.setAttribute("username",username); session.setAttribute("user",user); //response.sendRedirect("/myservlet2/admin/success.jsp"); //response.sendRedirect("https://www.bjsxt.com:443/news/11377.html"); //response.sendRedirect("http://localhost:8080/myservlet2/admin/success.jsp"); //response.sendRedirect("/myservlet2/admin/success.jsp"); //response.sendRedirect("/myservlet2/admin/success.jsp"); //response.sendRedirect(request.getContextPath()+"/admin/success.jsp"); //http://192.168.58.250:8080/myservlet2/servlet/LoginServlet //http://192.168.58.250:8080/myservlet2/admin/success.jsp //登录成功后,网站的访问人数+1 //1.获取当前的访问人数 ServletContext context = this.getServletContext(); Integer count2 = (Integer) context.getAttribute("count"); //2.人数+1 if(count2 == null){ //第一个用户 count2 = 1; }else{ count2++; } //3.再存放到application作用域中 context.setAttribute("count",count2); //http://192.168.58.250:8080/myservlet2/servlet/admin/success.jsp response.sendRedirect("../admin/success.jsp"); }else{ //失败跳转回登录页面 //out.println("登录失败"); request.setAttribute("error","用户名或者密码错误"); // RequestDispatcher rd = request.getRequestDispatcher("/admin/login.jsp"); // rd.forward(request,response); //RequestDispatcher rd = request.getRequestDispatcher("http://localhost:8080/myservlet2/admin/login.jsp"); //RequestDispatcher rd = request.getRequestDispatcher("/admin/login.jsp"); //http://192.168.58.250:8080/myservlet2/servlet/admin/login.jsp RequestDispatcher rd = request.getRequestDispatcher("../admin/login.jsp"); rd.forward(request,response); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值