springboot是搭建web微服务的简化框架,本文将springboot与scala和slick以及react集成,实现简单的前后端分离的restful api形式的web服务demo
其中
可以通过官网generate模板项目也可以建立空的maven工程再添加组件
在maven配置pom.xml里面需要加入
scala语言版本
.version>2.12.3 .version>
.compat.version>2.12 .compat.version>
scala语言编译
<dependency>
<groupId>org.scala-langgroupId>
<artifactId>scala-libraryartifactId>
<version>${scala.version}version>
dependency>
<dependency>
<groupId>org.scala-langgroupId>
<artifactId>scala-compilerartifactId>
<version>${scala.version}version>
dependency>
scala maven插件
<plugin>
<groupId>net.alchim31.mavengroupId>
<artifactId>scala-maven-pluginartifactId>
<version>3.2.1version>
<executions>
<execution>
<id>compile-scalaid>
<phase>compilephase>
<goals>
<goal>add-sourcegoal>
<goal>compilegoal>
goals>
execution>
<execution>
<id>test-compile-scalaid>
<phase>test-compilephase>
<goals>
<goal>add-sourcegoal>
<goal>testCompilegoal>
goals>
execution>
executions>
<configuration>
<recompileMode>incrementalrecompileMode>
<scalaVersion>${scala.version}scalaVersion>
<args>
<arg>-deprecationarg>
args>
<jvmArgs>
<jvmArg>-Xms64mjvmArg>
<jvmArg>-Xmx1024mjvmArg>
jvmArgs>
configuration>
plugin>
主类
package org.tashaxing.SpringbootScalaDemo
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.autoconfigure.{EnableAutoConfiguration, SpringBootApplication}
@SpringBootApplication
class BootConfig
object SpringbootScalaDemoApplication extends App {
SpringApplication.run(classOf[BootConfig])
}
controller里面的request mapping
@RestController // here must be restcontroller
@RequestMapping(Array("/scalatest"))
class ScalaTestController @Autowired()(private val scalaModelQuery: ScalaModelQuery)
{
// ---- normal operation
// root test
@GetMapping
def root = "hello scala springboot"
// get test with short mapping
@GetMapping(Array("/string1"))
def getTest1(): String = {
return "scala test1 results"
}
// get test with whole mapping
@RequestMapping(value = Array("/string2"), method = Array(RequestMethod.GET))
def getTest2(): String = {
return "scala test2 results"
}
// get int list
@GetMapping(Array("/list"))
def getIntList(): Array[Int] = {
return Array(1, 2, 3, 4)
}
}
springboot处理网络请求,如果想返回json格式的数据,需要根据springboot的规则定义数据结构,否则常规的class或者map之类数据无法正常被序列化
首先需要定义entity数据结构,可以写成case class和普通class,注意各种annotation的限定
package org.tashaxing.SpringbootScalaDemo.model
import java.lang.Long
import javax.persistence.{Entity, GeneratedValue, Id, Table}
import javax.validation.constraints.NotNull
import scala.beans.BeanProperty
import org.hibernate.validator.constraints.{NotBlank, NotEmpty}
import scala.annotation.meta.field
//@Table(name = "scala_model") // define mysql table name
//@Entity
//case class ScalaModel(
// @(Id @field) @(GeneratedValue @field) @BeanProperty var id: Long,
// @BeanProperty @(NotEmpty @field) var name: String, // @field is a must
// @BeanProperty @(NotEmpty @field) var age: Int
//)
@Table(name = "scala_model") // define mysql table name
@Entity
class ScalaModel
{
@Id
@GeneratedValue
@BeanProperty
var id: Long = _
@BeanProperty
@NotBlank // we can make sure it not empty
var name: String = _
@BeanProperty
@NotNull
var age: Int = 18 // we can define default value here
}
在controller中
// get object(frontend will get json structure)
@GetMapping(Array("/object"))
def getObject(): ScalaModel = {
val model = new ScalaModel // if it is defined with class
model.id = 3L
model.name = "lucy"
model.age = 21
// val model = ScalaModel(5L, "lily", 23) // if it is defined with abstract class
return model
}
pom.xml中添加jpa和mysql组件
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
创建数据库
mysql> create database db_example;
mysql> create user 'springuser'@'localhost' identified by 'ThePassword';
mysql> grant all on db_example.* to 'springuser'@'localhost'; the new user on the newly created database
配置数据库,在项目的properties文件中
#datasource
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/db_example?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=springuser
spring.datasource.password=ThePassword
注意要在model里面绑定表名
@Table(name = "scala_model")
构造repository和service套接层
repository用来绑定数据库表
package org.tashaxing.SpringbootScalaDemo.repository
import org.springframework.data.jpa.repository.{JpaRepository, Query}
import org.tashaxing.SpringbootScalaDemo.model.ScalaModel
// remember to use java List and Long here
import java.util.List
import java.lang.Long
trait ScalaModelRepository extends JpaRepository[ScalaModel, Long]
{
// normal find function from db
def findByName(name: String): List[ScalaModel]
// self defined function from db by sql query
// @Query(value = "select name from scala_model where age < 22")
// def findNameOfAge(): List[Object]
}
service用来定义实际的数据库操作
package org.tashaxing.SpringbootScalaDemo.repository
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.beans.factory.annotation.Autowired
import scala.reflect.ClassTag
import java.lang.Long
import org.springframework.data.domain.Page
import java.util.List
import org.springframework.stereotype.Service
import javax.transaction.Transactional
import java.lang.Boolean
import org.springframework.data.domain.PageRequest
// base query helper for all datatype and table
@Service
abstract class BaseQuery[T: ClassTag] {
/** spring data jpa dao */
@Autowired val jpaRepository: JpaRepository[T, Long] = null
/**
* add record
*
* @param s
* @return T
*/
def save[S <: T](s: S): T = jpaRepository.save(s)
/**
* delete record by id
*
* @param id 数据Id
* @return Unit
*/
@Transactional
def delete(id: Long): Unit = jpaRepository.delete(id)
/**
* delete by batch
*
* @param lists
* @return Unit
*/
@Transactional
def delete(lists: List[T]): Unit = jpaRepository.delete(lists);
/**
* update recorde by id
*
* @param s
* @return T
*/
@Transactional
def update[S <: T](s: S) = jpaRepository.save(s)
/**
* find recorde by id
*
* @param id 数据Id
* @return T
*/
def find[S <: T](id: Long): T = jpaRepository.findOne(id)
/**
* query all
*
* @return List[T]
*/
def findAll[S <: T]: List[T] = jpaRepository.findAll
/**
* query by id batch
*
* @return List[T]
*/
def findAll[S <: T](ids: List[Long]): List[T] = jpaRepository.findAll(ids)
/**
* count
*
* @return Long
*/
def count: Long = jpaRepository.count
/**
* check recorde exits
*
* @param id
* @return Boolean
*/
def exist(id: Long): Boolean = jpaRepository.exists(id)
/**
* query page
*
* @param page start page
* @param pageSize page number
* @return Page[T]
*/
def page[S <: T](page: Int, pageSize: Int): Page[T] = {
var rpage = if (page < 1) 1 else page;
var rpageSize = if (pageSize < 1) 5 else pageSize;
jpaRepository.findAll(new PageRequest(rpage - 1, pageSize))
}
}
controller中添加restful的api用于数据库增删改查
// get
@GetMapping(value = Array("/findid/{id}"))
def findById(@PathVariable(value = "id") id: Long): ScalaModel =
{
return scalaModelQuery.find(id)
}
@GetMapping(Array("/findname/{name}"))
def findByName(@PathVariable("name") name: String): List[ScalaModel] =
{
return scalaModelQuery.findByName(name)
}
// post
@PostMapping(Array("/delete/{id}"))
def deleteById(@PathVariable("id") id: Long): Unit =
{
val res = scalaModelQuery.delete(id)
return res
}
@RequestMapping(value = Array("/add"), method = Array(RequestMethod.POST))
def save(@RequestBody scalaModel: ScalaModel): ScalaModel =
{
// notice here use RequestBody
val res = scalaModelQuery.save(scalaModel)
return res
}
@PostMapping(Array("/update"))
def update(@RequestBody scalaModel: ScalaModel, bindingResult: BindingResult): ScalaModel =
{
val res = scalaModelQuery.update(scalaModel)
return res
}
slick是基于scala的数据库连接中间件,具备跟scala一样方便的函数式编程接口,可以简化数据访问过程,推荐使用,这里将在springboot中集成slick
首先在maven中添加slick的依赖
<dependency>
<groupId>com.typesafe.slickgroupId>
<artifactId>slick_2.12artifactId>
<version>3.2.1version>
dependency>
配置连接
val db = Database.forURL(
url = "jdbc:mysql://localhost:3306/db_example?useUnicode=true&characterEncoding=UTF-8&useSSL=false",
driver = "com.mysql.jdbc.Driver",
user = "springuser",
password = "ThePassword")
添加slick绑定的model
case class UserInfo(id: Long, name: String, age: Int)
// define an entity class with contructor used for spring serialize
@Entity
class UserInfoObject
{
@Id
@GeneratedValue
@BeanProperty
var id: Long = _
@BeanProperty
@NotBlank // we can make sure it not empty
var name: String = _
@BeanProperty
@NotNull
var age: Int = 18 // we can define default value here
}
class SlickModelTable(tag: Tag) extends Table[UserInfo](tag, "scala_model")
{
def id = column[Long]("id", O.PrimaryKey)
def name = column[String]("name")
def age = column[Int]("age")
def * = (id, name, age) <> (UserInfo.tupled, UserInfo.unapply)
}
def slick_table = TableQuery[SlickModelTable]
controller中添加api用数据库增删改查
@GetMapping(Array("/getslick/{name}"))
def getSlickModel(@PathVariable("name") name: String): UserInfoObject =
{
val slickres = Await.result(db.run(slick_table.filter(_.name === name).result), Duration.Inf)
val userInfoObject = new UserInfoObject
userInfoObject.id = slickres(0).id
userInfoObject.name = slickres(0).name
userInfoObject.age = slickres(0).age
return userInfoObject
}
@GetMapping(Array("/listslick"))
def listSlickModel(): Array[UserInfoObject] =
{
val slickres = Await.result(db.run(slick_table.result), Duration.Inf)
// val slickres = Await.result(db.run(slick_table.filter(_.age < 22).result), Duration.Inf)
val res = mutable.ArrayBuffer[UserInfoObject]()
slickres.map(
record => {
val userInfoObject = new UserInfoObject
userInfoObject.id = record.id
userInfoObject.name = record.name
userInfoObject.age = record.age
res += userInfoObject
})
return res.toArray
}
@PostMapping(Array("/slickadd"))
def slicksave(@RequestBody userInfoObject: UserInfoObject): String =
{
val userInfo = UserInfo(userInfoObject.id, userInfoObject.name, userInfoObject.age)
val userArray = Array[UserInfo](userInfo)
Await.result(db.run(slick_table ++= userArray), Duration.Inf)
return "save success"
}
@PostMapping(Array("/slickupdate"))
def slickupdate(@RequestBody userInfoObject: UserInfoObject): String =
{
val userInfo = UserInfo(userInfoObject.id, userInfoObject.name, userInfoObject.age)
val userArray = Array[UserInfo](userInfo)
Await.result(db.run(slick_table.filter(_.name === userInfo.name).delete), Duration.Inf)
Await.result(db.run(slick_table ++= userArray), Duration.Inf)
return "update success"
}
@PostMapping(Array("/slickdelete/{id}"))
def slickdelete(@PathVariable("id") id: Long): String =
{
Await.result(db.run(slick_table.filter(_.id === id).delete), Duration.Inf)
return "delete success"
}
为了调试方便,可以设置springboot的cors,支持外部IP的跨域访问
package org.tashaxing.SpringbootScalaDemo.config
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.{CorsRegistry, WebMvcConfigurerAdapter}
@Configuration
class OriginConfig extends WebMvcConfigurerAdapter
{
override def addCorsMappings(registry: CorsRegistry): Unit =
{
// cors setting to allow origin access
registry.addMapping("/**") // allow any path
.allowedOrigins("http://192.168.1.97") // allow exact ip /** to map any ip
.allowedMethods("GET", "POST") // allow methods
.allowedHeaders("*") // allow any header
}
}
或者单独在controller某个function的mapping前面加入
@CrossOrigin(origins = "http://192.168.1.97:8080", maxAge = 3600)
@GetMapping(Array("/string1"))
构造react前端项目,用webpack打包到springboot的recource的静态页面目录
react向后台发送网络请求来操作数据库
// update one record
axios({
method: 'post',
url: '/scalatest/update',
data: {
id: 3,
name: "tashaxing",
age: 25
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
scala在springboot中的重定向写法与java类似
package org.tashaxing.SpringbootScalaDemo.controller
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
@Controller
class PageController
{
// root page
@RequestMapping(Array("/"))
def index(): String = "redirect:/index.html"
// an other page
// use forward instead of redirect will not show xxx.html in browser address bar
@RequestMapping(Array("/subpage"))
def sub(): String = "forward:/subpage/newpage.html"
// no need to redirect apitest
// @RequestMapping(Array("/apitest"))
// def apitest(): String = "redirect:/apitest.html"
}
命令行
Action | Maven |
---|---|
clean | mvn clean |
run | mvn scala:run |
package | mvn package |
如果使用IDE
直接启动SpringbootScalaDemoApplication运行即可
(1)由springboot来作为webserver和http服务器
react项目build到springboot文件夹,然后启动springboot主程序,会启动自带的tomcat web容器,将react的静态页面host起来,前端和后台完全分离,通过restful api交互
比如:
在浏览器中输入
http://localhost:7777/scalatest/list
会得到
[1,2,3,4]
浏览器定向到react的页面
http://localhost:7777/apitest.html
在前端执行添加数据的请求
// save one record
axios({
method: 'post',
url: '/scalatest/add',
data: {
id: 7,
name: "tashaxing",
age: 25
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
会在数据库增加一条记录,返回
age:25
id:7
name:"tashaxing"
(2)用nginx作为webserver并添加反向代理
原理是用nginx来host静态页面,而该页面的http请求会重定向到springboot的http服务,实现完美的前后端分离
nginx配置
server {
listen 80;
server_name localhost;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /scalatest {
proxy_pass http://localhost:7700/scalatest/;
proxy_set_header Host $host; # 非常重要,缺少的话POST请求通不过
}
}
这样用户访问localhost:80会打开静态页面,而localhost:80/scalatest开头的请求会被路由到springboot,当然前提是跨域功能要打开
spring.jpa.hibernate.ddl-auto=none
csdn:springboot_scala_react
github:springboot_scala_react