浅谈Slick(4)- Slick301:我的Slick开发项目设置

2024-04-09 04:58

本文主要是介绍浅谈Slick(4)- Slick301:我的Slick开发项目设置,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  前面几篇介绍里尝试了一些Slick的功能和使用方式,看来基本可以满足用scala语言进行数据库操作编程的要求,而且有些代码可以通过函数式编程模式来实现。我想,如果把Slick当作数据库操作编程主要方式的话,可能需要先制定一套比较规范的模式来应付日常开发(也要考虑团队开发)、测试和维护。首先从项目结构来说,我发现由Intellij-Idea IDE界面直接产生的SBT项目结构已经比较理想了。在src/main/resources是scala项目获取配置文件的默认目录、我们可以按照需要在src/main/scala下增加代码子目录(package)及在src/main/test下摆放测试代码。配置文件application.conf、logback.xml是放在src/main/resources下的。application.conf是Slick的配置文件,logback.xml是跟踪器logback(log4j)的配置文件。Slick把jdbc api集成到scala编程语言里,能够支持多种数据库。也就是说Slick提供了多种数据库的驱动api。Slick支持在配置文件application.conf里配置数据库功能模式,这样我们就可以在正式部署软件时才通过修订application.conf里的配置来决定具体的数据库种类和参数。当然前提是我们的程序代码不能依赖任何特别的数据库api。我们从表结构设定开始,先看看上篇Slick101里的例子:

package com.datatech.learn.slick101
import slick.driver.H2Driver.api._
object slick101 {/* ----- schema  *///表字段对应模版case class AlbumModel (id: Long,title: String,year: Option[Int],artist: String)//表结构: 定义字段类型, * 代表结果集字段class AlbumTable(tag: Tag) extends Table[AlbumModel](tag, "ALBUMS") {def id = column[Long]("ID",O.AutoInc,O.PrimaryKey)def title = column[String]("TITLE")def year = column[Option[Int]]("YEAR")def artist = column[String]("ARTIST",O.Default("Unknown"))def * = (id,title,year,artist) <> (AlbumModel.tupled, AlbumModel.unapply)}//库表实例val albums = TableQuery[AlbumTable]


我们可以看到这段代码依赖了slick.driver.H2Driver.api,是专门针对H2 Database的了。我们可以用依赖注入(dependency injection, IOC)来解决这个依赖问题。先试试用最传统的依赖注入方式:传入参数来注入这个数据库驱动依赖, 把代码放在src/main/scala/model/TableDefs.scala里:

package com.bayakala.learn.slick301.model
import slick.driver.JdbcProfile
class TableDefs(val dbDriver: JdbcProfile) {import dbDriver.api._case class Supplier(id: Long, name: String, contact: Option[String], website: Option[String])final class Suppliers(tag: Tag) extends Table[Supplier](tag,"SUPPLERS") {def id = column[Long]("ID",O.AutoInc,O.PrimaryKey)def name = column[String]("NAME")def contact = column[Option[String]]("CONTACT")def website = column[Option[String]]("WEBSITE")def * = (id, name, contact, website) <> (Supplier.tupled,Supplier.unapply)def nidx = index("NM_IDX",name,unique = true)}val suppliers = TableQuery[Suppliers]case class Coffee(id: Long,name: String,supid: Long,price: Double,sales: Int)final class Coffees(tag: Tag) extends Table[Coffee](tag, "COFFEES") {def id = column[Long]("ID",O.AutoInc,O.PrimaryKey)def name = column[String]("NAME")def supid = column[Long]("SUPID")def price = column[Double]("PRICE",O.Default(0.0))def sales = column[Int]("SALES",O.Default(0))def * = (id,name,supid,price,sales) <> (Coffee.tupled, Coffee.unapply)def fk_sup = foreignKey("FK_SUP",supid,suppliers)(_.id,onDelete = ForeignKeyAction.Restrict,onUpdate = ForeignKeyAction.Cascade)def supidx = index("SUP_IDX",supid,unique = false)def nidx = index("NM_IDX",name,unique = true)}val coffees = TableQuery[Coffees]}


注意我们是把JdbcProfile作为参数注入了class TableDefs里。如果TableDefs经常需要作为其它类的父类继承的话,设计成trait能更加灵活的进行类型混合(type mixing)。这样的需求可以用cake pattern方式进行依赖注入。我们在需要src/main/scala/config/AppConfig.scala里定义依赖界面trait DBConfig:

package com.bayakala.learn.slick301.config
import slick.driver.JdbcProfile
trait DBConfig {val jdbcDriver: JdbcProfileimport jdbcDriver.api._val db: Database
}


后面我们可以通过实现多种DBConfig实例方式来构建开发、测试、部署等数据库环境。为了方便示范,我们设计几个基本的Query Action,放在src/main/scala/access/DAOs.scala里,用cake pattern注入依赖DBConfig:

package com.bayakala.learn.slick301.access
import com.bayakala.learn.slick301.config
import com.bayakala.learn.slick301.config.DBConfig
import com.bayakala.learn.slick301.model.TableDefs
trait DAOs { dbconf: DBConfig =>import jdbcDriver.api._//注入依赖val tables = new TableDefs(dbconf.jdbcDriver)import tables._//suppliers queriesval createSupplierTable = suppliers.schema.createval allSuppliers = suppliers.resultdef insertSupplier(id:Long,name:String,address:Option[String],website:Option[String])= suppliers += Supplier(id,name,address,website)def insertSupbyName(n: String) = suppliers.map(_.name) += n//coffees queriesval createCoffeeTable = coffees.schema.createval allCoffees = coffees.resultdef insertCoffee(c: (Long,String,Long,Double,Int)) =coffees += Coffee(id=c._1, name=c._2,supid=c._3,price=c._4,sales=c._5)}


dbconf: DBConfig => 的意思是在进行DAOs的实例化时必须混入(mixing)DBConfig类。

以上两个代码文件TableDefs.scala和DAOs.scala在注入依赖后都能够顺利通过编译了。

我们在src/main/scala/main/Main.scala里测试运算DAOs里的query action:

package com.bayakala.learn.slick301.main
import com.bayakala.learn.slick301.config.DBConfig
import com.bayakala.learn.slick301.access.DAOsimport scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import slick.backend.DatabaseConfig
import slick.driver.{H2Driver, JdbcProfile}
object Main {object Actions extends DAOs with DBConfig {override lazy val jdbcDriver: JdbcProfile = H2Driverval dbConf: DatabaseConfig[H2Driver] = DatabaseConfig.forConfig("h2")override val db = dbConf.db}import Actions._def main(args: Array[String]) = {val res = db.run(createSupplierTable).andThen {case Success(_) => println("supplier table created")case Failure(_) => println("unable to create supplier table")}Await.ready(res, 3 seconds)val res2 = db.run(insertSupbyName("Acme Coffee Co."))Await.ready(res2, 3 seconds)Await.ready(db.run(allSuppliers), 10 seconds).foreach(println)val res10 = db.run(createCoffeeTable).andThen {case Success(_) => println("coffee table created")case Failure(_) => println("unable to create coffee table")}Await.ready(res10, 3 seconds)val res11 = db.run(insertCoffee((101,"Columbia",1,158.0,0)))Await.ready(res11, 3 seconds)Await.ready(db.run(allCoffees), 10 seconds).foreach(println)}}


Actions是DAOs的实例。我们看到必须把DBConfig混入(mixin)。但是我们构建的数据库又变成了专门针对H2的api了,这样的话每次变动数据库对象我们就必须重新编译Main.scala,不符合上面我们提到的要求。我们可以把目标数据库放到application.conf里,然后在Main.scala里用typesafe-config实时根据application.conf里的设置确定数据库参数。src/main/resources/application.conf内容如下:

app = {dbconfig = h2
}h2 {driver = "slick.driver.H2Driver$"db {url = "jdbc:h2:~/slickdemo;mv_store=false"driver = "org.h2.Driver"connectionPool = HikariCPnumThreads = 10maxConnections = 12minConnections = 4keepAliveConnection = true}
}h2mem = {url = "jdbc:h2:mem:slickdemo"driver = org.h2.DriverconnectionPool = disabledkeepAliveConnection = true
}mysql {driver = "slick.driver.MySQLDriver$"db {url = "jdbc:mysql://localhost/slickdemo"driver = com.mysql.jdbc.DriverkeepAliveConnection = trueuser="root"password="123"numThreads=10maxConnections = 12minConnections = 4}
}mysqldb = {dataSourceClass = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource"properties {user = "root"password = "123"databaseName = "slickdemo"serverName = "localhost"}numThreads = 10maxConnections = 12minConnections = 4
}postgres {driver = "slick.driver.PostgresDriver$"db {url = "jdbc:postgresql://127.0.0.1/slickdemo"driver = "org.postgresql.Driver"connectionPool = HikariCPuser = "slick"password = "123"numThreads = 10maxConnections = 12minConnections = 4}
}postgressdb = {dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"properties = {databaseName = "slickdemo"user = "slick"password = "123"}connectionPool = HikariCPnumThreads = 10maxConnections = 12minConnections = 4
}mssql {driver = "com.typesafe.slick.driver.ms.SQLServerDriver$"db {url = "jdbc:sqlserver://host:port"driver = com.microsoft.sqlserver.jdbc.SQLServerDriverconnectionTimeout = 30 secondconnectionPool = HikariCPuser = "slick"password = "123"numThreads = 10maxConnections = 12minConnections = 4keepAliveConnection = true}
}tsql {driver = "slick.driver.H2Driver$"db = ${h2mem}
}


现在application.conf里除了数据库配置外又加了个app配置。我们在Main.scala里实例化DAOs时可以用typesafe-config读取app.dbconfig值后设定jdbcDriver和db:

  object Actions extends DAOs with DBConfig {import slick.util.ClassLoaderUtilimport scala.util.control.NonFatalimport com.typesafe.config.ConfigFactorydef getDbConfig: String =ConfigFactory.load().getString("app.dbconfig")def getDbDriver(path: String): JdbcProfile = {val config = ConfigFactory.load()val n = config.getString((if (path.isEmpty) "" else path + ".") + "driver")val untypedP = try {if (n.endsWith("$")) ClassLoaderUtil.defaultClassLoader.loadClass(n).getField("MODULE$").get(null)else ClassLoaderUtil.defaultClassLoader.loadClass(n).newInstance()} catch {case NonFatal(ex) =>throw new SlickException(s"""Error getting instance of Slick driver "$n"""", ex)}untypedP.asInstanceOf[JdbcProfile]}override lazy val jdbcDriver: JdbcProfile = getDbDriver(getDbConfig)val dbConf: DatabaseConfig[JdbcProfile] = DatabaseConfig.forConfig(getDbConfig)override val db = dbConf.db}


现在我们只需要改变application.conf里的app.dbconfig就可以转换目标数据库参数了。实际上,除了数据库配置,我们还可以在application.conf里进行其它类型的配置。然后用typesafe-config实时读取。如果不想在application.conf进行数据库之外的配置,可以把其它配置放在任何文件里,然后用ConfigFactory.load(path)来读取。

另外,在软件开发过程中跟踪除错也是很重要的。我们可以用logback来跟踪Slick、HikariCP等库的运行状态。logback配置在src/main/resources/logback.xml:

<?xml version="1.0" encoding="UTF-8"?><configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><logger name="application" level="DEBUG"/><logger name="com.zaxxer.hikari" level="DEBUG"/><logger name="slick" level="DEBUG"/><root level="DEBUG"><appender-ref ref="STDOUT"/></root>
</configuration>


DEBUG值可以显示最详细的状态信息。

好了,我把这次示范代码提供在下面:

build.sbt:

name := "learn-slick301"version := "1.0"scalaVersion := "2.11.8"libraryDependencies ++= Seq("com.typesafe.slick" %% "slick" % "3.1.1","com.h2database" % "h2" % "1.4.191","com.typesafe.slick" %% "slick-hikaricp" % "3.1.1","ch.qos.logback" % "logback-classic" % "1.1.7","org.typelevel" %% "cats" % "0.7.2")


src/main/resources/

application.conf:

app = {dbconfig = h2
}h2 {driver = "slick.driver.H2Driver$"db {url = "jdbc:h2:~/slickdemo;mv_store=false"driver = "org.h2.Driver"connectionPool = HikariCPnumThreads = 10maxConnections = 12minConnections = 4keepAliveConnection = true}
}h2mem = {url = "jdbc:h2:mem:slickdemo"driver = org.h2.DriverconnectionPool = disabledkeepAliveConnection = true
}mysql {driver = "slick.driver.MySQLDriver$"db {url = "jdbc:mysql://localhost/slickdemo"driver = com.mysql.jdbc.DriverkeepAliveConnection = trueuser="root"password="123"numThreads=10maxConnections = 12minConnections = 4}
}mysqldb = {dataSourceClass = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource"properties {user = "root"password = "123"databaseName = "slickdemo"serverName = "localhost"}numThreads = 10maxConnections = 12minConnections = 4
}postgres {driver = "slick.driver.PostgresDriver$"db {url = "jdbc:postgresql://127.0.0.1/slickdemo"driver = "org.postgresql.Driver"connectionPool = HikariCPuser = "slick"password = "123"numThreads = 10maxConnections = 12minConnections = 4}
}postgressdb = {dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"properties = {databaseName = "slickdemo"user = "slick"password = "123"}connectionPool = HikariCPnumThreads = 10maxConnections = 12minConnections = 4
}mssql {driver = "com.typesafe.slick.driver.ms.SQLServerDriver$"db {url = "jdbc:sqlserver://host:port"driver = com.microsoft.sqlserver.jdbc.SQLServerDriverconnectionTimeout = 30 secondconnectionPool = HikariCPuser = "slick"password = "123"numThreads = 10maxConnections = 12minConnections = 4keepAliveConnection = true}
}tsql {driver = "slick.driver.H2Driver$"db = ${h2mem}
}


logback.xml:

<?xml version="1.0" encoding="UTF-8"?><configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><logger name="application" level="DEBUG"/><logger name="com.zaxxer.hikari" level="DEBUG"/><logger name="slick" level="DEBUG"/><root level="DEBUG"><appender-ref ref="STDOUT"/></root>
</configuration>


src/main/scala/config/AppConfig.scala:

package com.bayakala.learn.slick301.config
import slick.driver.JdbcProfile
trait DBConfig {val jdbcDriver: JdbcProfileimport jdbcDriver.api._val db: Database
}

src/main/scala/model/TableDefs.scala:

package com.bayakala.learn.slick301.model
import slick.driver.JdbcProfile
class TableDefs(val dbDriver: JdbcProfile) {import dbDriver.api._case class Supplier(id: Long, name: String, contact: Option[String], website: Option[String])final class Suppliers(tag: Tag) extends Table[Supplier](tag,"SUPPLERS") {def id = column[Long]("ID",O.AutoInc,O.PrimaryKey)def name = column[String]("NAME")def contact = column[Option[String]]("CONTACT")def website = column[Option[String]]("WEBSITE")def * = (id, name, contact, website) <> (Supplier.tupled,Supplier.unapply)def nidx = index("NM_IDX",name,unique = true)}val suppliers = TableQuery[Suppliers]case class Coffee(id: Long,name: String,supid: Long,price: Double,sales: Int)final class Coffees(tag: Tag) extends Table[Coffee](tag, "COFFEES") {def id = column[Long]("ID",O.AutoInc,O.PrimaryKey)def name = column[String]("NAME")def supid = column[Long]("SUPID")def price = column[Double]("PRICE",O.Default(0.0))def sales = column[Int]("SALES",O.Default(0))def * = (id,name,supid,price,sales) <> (Coffee.tupled, Coffee.unapply)def fk_sup = foreignKey("FK_SUP",supid,suppliers)(_.id,onDelete = ForeignKeyAction.Restrict,onUpdate = ForeignKeyAction.Cascade)def supidx = index("SUP_IDX",supid,unique = false)def nidx = index("NM_IDX",name,unique = true)}val coffees = TableQuery[Coffees]}


src/main/scala/access/DAOs.scala:

package com.bayakala.learn.slick301.access
import com.bayakala.learn.slick301.config
import com.bayakala.learn.slick301.config.DBConfig
import com.bayakala.learn.slick301.model.TableDefs
trait DAOs { dbconf: DBConfig =>import jdbcDriver.api._//注入依赖val tables = new TableDefs(dbconf.jdbcDriver)import tables._//suppliers queriesval createSupplierTable = suppliers.schema.createval allSuppliers = suppliers.resultdef insertSupplier(id:Long,name:String,address:Option[String],website:Option[String])= suppliers += Supplier(id,name,address,website)def insertSupbyName(n: String) = suppliers.map(_.name) += n//coffees queriesval createCoffeeTable = coffees.schema.createval allCoffees = coffees.resultdef insertCoffee(c: (Long,String,Long,Double,Int)) =coffees += Coffee(id=c._1, name=c._2,supid=c._3,price=c._4,sales=c._5)}


src/main/scala/main/Main.scala:

package com.bayakala.learn.slick301.main
import com.bayakala.learn.slick301.config.DBConfig
import com.bayakala.learn.slick301.access.DAOsimport scala.concurrent.Await
import scala.util.{Failure, Success}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import slick.backend.DatabaseConfig
import slick.driver.JdbcProfileobject Main {object Actions extends DAOs with DBConfig {import slick.SlickExceptionimport slick.util.ClassLoaderUtilimport scala.util.control.NonFatalimport com.typesafe.config.ConfigFactorydef getDbConfig: String =ConfigFactory.load().getString("app.dbconfig")def getDbDriver(path: String): JdbcProfile = {val config = ConfigFactory.load()val n = config.getString((if (path.isEmpty) "" else path + ".") + "driver")val untypedP = try {if (n.endsWith("$")) ClassLoaderUtil.defaultClassLoader.loadClass(n).getField("MODULE$").get(null)else ClassLoaderUtil.defaultClassLoader.loadClass(n).newInstance()} catch {case NonFatal(ex) =>throw new SlickException(s"""Error getting instance of Slick driver "$n"""", ex)}untypedP.asInstanceOf[JdbcProfile]}override lazy val jdbcDriver: JdbcProfile = getDbDriver(getDbConfig)val dbConf: DatabaseConfig[JdbcProfile] = DatabaseConfig.forConfig(getDbConfig)override val db = dbConf.db}import Actions._def main(args: Array[String]) = {val res = db.run(createSupplierTable).andThen {case Success(_) => println("supplier table created")case Failure(_) => println("unable to create supplier table")}Await.ready(res, 3 seconds)val res2 = db.run(insertSupbyName("Acme Coffee Co."))Await.ready(res2, 3 seconds)Await.ready(db.run(allSuppliers), 10 seconds).foreach(println)val res10 = db.run(createCoffeeTable).andThen {case Success(_) => println("coffee table created")case Failure(_) => println("unable to create coffee table")}Await.ready(res10, 3 seconds)val res11 = db.run(insertCoffee((101,"Columbia",1,158.0,0)))Await.ready(res11, 3 seconds)Await.ready(db.run(allCoffees), 10 seconds).foreach(println)}
















这篇关于浅谈Slick(4)- Slick301:我的Slick开发项目设置的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887223

相关文章

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Three.js构建一个 3D 商品展示空间完整实战项目

《Three.js构建一个3D商品展示空间完整实战项目》Three.js是一个强大的JavaScript库,专用于在Web浏览器中创建3D图形,:本文主要介绍Three.js构建一个3D商品展... 目录引言项目核心技术1. 项目架构与资源组织2. 多模型切换、交互热点绑定3. 移动端适配与帧率优化4. 可

sky-take-out项目中Redis的使用示例详解

《sky-take-out项目中Redis的使用示例详解》SpringCache是Spring的缓存抽象层,通过注解简化缓存管理,支持Redis等提供者,适用于方法结果缓存、更新和删除操作,但无法实现... 目录Spring Cache主要特性核心注解1.@Cacheable2.@CachePut3.@Ca

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

SpringBoot通过main方法启动web项目实践

《SpringBoot通过main方法启动web项目实践》SpringBoot通过SpringApplication.run()启动Web项目,自动推断应用类型,加载初始化器与监听器,配置Spring... 目录1. 启动入口:SpringApplication.run()2. SpringApplicat

基于Java开发一个极简版敏感词检测工具

《基于Java开发一个极简版敏感词检测工具》这篇文章主要为大家详细介绍了如何基于Java开发一个极简版敏感词检测工具,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录你是否还在为敏感词检测头疼一、极简版Java敏感词检测工具的3大核心优势1.1 优势1:DFA算法驱动,效率提升10

Springboot项目构建时各种依赖详细介绍与依赖关系说明详解

《Springboot项目构建时各种依赖详细介绍与依赖关系说明详解》SpringBoot通过spring-boot-dependencies统一依赖版本管理,spring-boot-starter-w... 目录一、spring-boot-dependencies1.简介2. 内容概览3.核心内容结构4.

MySQL设置密码复杂度策略的完整步骤(附代码示例)

《MySQL设置密码复杂度策略的完整步骤(附代码示例)》MySQL密码策略还可能包括密码复杂度的检查,如是否要求密码包含大写字母、小写字母、数字和特殊字符等,:本文主要介绍MySQL设置密码复杂度... 目录前言1. 使用 validate_password 插件1.1 启用 validate_passwo

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块