Ecto 总结

简介:

ecto 简介

ecto 相当于 elixir 的 ORM,但是得益于 elixir 语言,和传统的 ORM 相比,更加简洁和强大。
ecto 主要分为 4 部分:

  1. Repo: 这是和真正数据库交互的部分
  2. Schema: 相当于是数据库中表的定义,但不仅仅是定义
  3. Changeset:Schema 到真正数据库之间的转换层
  4. Query:elixir 风格的数据查询方式

ecto 连接数据库(以 sqlite 为例)

连接到真实的数据库,需要相应的驱动,下面以 sqlite 为例,配置数据库的连接

创建工程

也就是一般的 elixir 工程

mix new ecto_sample

引入 ecto 和数据库驱动

引入 ecto 和 sqlite 相关的 packages (mix.exs)

defp deps do
  [
    {:sqlite_ecto2, "~> 2.2"},
    {:ecto, "~> 2.1"}
  ]
end

配置好后通过命令行安装:

mix deps.get

创建数据库

在 lib/ecto_sample 下创建文件 repo.ex,文件内容如下:

defmodule EctoSample.Repo do
  use Ecto.Repo,
    otp_app: :ecto_sample,
    adapter: Sqlite.Ecto2
end

配置数据库连接,config/config.esx

config :ecto_sample, ecto_repos: [EctoSample.Repo]  # 配置 repo

config :ecto_sample, EctoSample.Repo,   # 配置驱动和数据库位置,这里用的 sqlite 数据,比较简单
  adapter: Sqlite.Ecto2,
  database: "ecto_sample.db"

sqlite 数据库只要配置数据库文件的位置即可,如果是 postgres 之类的关系数据库,需要配置主机,用户名/密码 等

config :ecto_sample, EctoSample.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "ecto_sample_repo",
  username: "user",
  password: "pass",
  hostname: "localhost"

配置完成后,创建数据库

mix ecto.create

成功执行的话,能看到在工程根目录下多了个 ecto_sample.db 文件

创建表

创建 schema users, lib/repo.ex 文件中追加
其中 changeset 是在更新数据时用来验证数据有效性或者转换数据用的,不是必须的

defmodule EctoSample.User do
  use Ecto.Schema

  schema "users" do
    field :username, :string
    field :password, :string
    field :email,    :string
    field :age,      :integer
  end

  def changeset(user, params \\ %{}) do
    user
    |> cast(params, [:username, :password, :email, :age])
    |> validate_required([:username, :password])
  end
end

创建建表的 migration

$ mix ecto.gen.migration create_user
Compiling 2 files (.ex)
Generated ecto_sample app
* creating priv/repo/migrations
* creating priv/repo/migrations/20171123012930_create_user.exs

参照 users 的 schema 编辑 priv/repo/migrations/20171123012930_create_user.exs

defmodule EctoSample.Repo.Migrations.CreateUser do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :username, :string
      add :password, :string
      add :email,    :string
      add :age,      :integer
    end
  end
end

创建表

$ mix ecto.migrate

09:33:40.257 [info]  == Running EctoSample.Repo.Migrations.CreateUser.change/0 forward

09:33:40.257 [info]  create table users

09:33:40.259 [info]  == Migrated in 0.0s

登入数据库验证

用 sqlite3 的客户端登入数据库查看情况,下面使用的是命令行方式

$ sqlite3 ecto_sample.db 
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
sqlite> .fullschema
CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" BIGINT PRIMARY KEY, "inserted_at" NAIVE_DATETIME);
CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY, "username" TEXT, "password" TEXT, "email" TEXT, "age" INTEGER);
/* No STAT tables available */
sqlite> .exit

可以看出:

  1. 除了创建了 users 表,ecto 还创建了 schema_migrations 用来管理每次的 migration
  2. 默认创建了 id 主键,类型是 INTEGER,如果要改成 uuid 创建时要明确指定

ecto 单表操作

演示示例

通过一个例子来演示对 users 表的 CURD
示例流程: 增加一个记录 -> 查询这条记录 -> 修改这条记录 -> 查询新的记录 -> 删除这条记录 -> 再次查询为空

  1. 新增记录

    def add_user(username, password, email \\ "", age \\ 0) do
      user = EctoSample.User.changeset(%EctoSample.User{}, %{:username => username, :password => password,
                                                             :email => email, :age => age})
    
      case EctoSample.Repo.insert(user) do
        {:ok, _} -> Logger.info "insert successfully"
        {:error, _} -> Logger.error "insert failed"
      end
    end
  2. 查询记录

    import Ecto.Query, only: [from: 2]
    
    q = from u in EctoSample.User,
      where: u.username == ^username
    
    EctoSample.Repo.all(q) |> Enum.map(fn (u) ->
      Logger.info "==============================="
      Logger.info "username: " <> u.username
      Logger.info "password: " <> u.password
      if u.email do
        Logger.info "email:    " <> u.email
      end
      Logger.info "age:      " <> Integer.to_string u.age
      Logger.info "==============================="
    end)
  3. 修改记录

    def change_user(id, params \\ %{}) do
      u = EctoSample.Repo.get!(EctoSample.User, id)
      changeset = EctoSample.User.changeset(u, params)
    
      EctoSample.Repo.update(changeset)
    end
  4. 删除记录

    def delete_user(id) do
      u = EctoSample.Repo.get!(EctoSample.User, id)
      EctoSample.Repo.delete(u)
      |> case  do
           {:ok, _} -> Logger.info "delete successfully"
           {:error, _} -> Logger.error "delete failed"
         end
    end

测试步骤

$ iex -S mix
  Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [hipe] [kernel-poll:false]

  Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> EctoSample.start
  {:ok, #PID<0.182.0>}
iex(2)> EctoSample.add_user("a", "b", "c", 10)

  22:45:22.570 [info]  insert successfully
iex(3)> EctoSample.query_user("a")

  22:45:29.370 [info]  ===============================
  [:ok]

  22:45:29.370 [info]  username: a

  22:45:29.370 [info]  password: b

  22:45:29.370 [info]  email:    c

  22:45:29.370 [info]  age:      10

  22:45:29.370 [info]  ===============================

iex(4)> EctoSample.change_user(1, %{:username => "change", :age => 20})
  {:ok,
   %EctoSample.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, age: 20,
    email: "c", id: 1, password: "b", username: "change"}}
iex(5)> EctoSample.query_user("change")

  22:48:47.644 [info]  ===============================

  22:48:47.644 [info]  username: change

  22:48:47.644 [info]  password: b

  22:48:47.644 [info]  email:    c

  22:48:47.644 [info]  age:      20

  22:48:47.644 [info]  ===============================
iex(6)> EctoSample.delete_user(1)

  22:50:17.848 [info]  delete successfully
iex(7)> EctoSample.query_user("change")

  []

ecto 表关系操作

表关系只有 3 种,1:1,1:N,M:N。 先在现有的表基础上增加 3 张表

增加和 User 关联的表

  1. table 定义

    defmodule EctoSample.Schema.User do
      use Ecto.Schema
      import Ecto.Changeset
    
      schema "users" do
        field :username, :string
        field :password, :string
        field :email,    :string
        field :age,      :integer
    
        has_one :computer, EctoSample.Schema.Computer
        belongs_to :company, EctoSample.Schema.Company
        many_to_many :friends, EctoSample.Schema.Friend, join_through: "users_friends"
      end
    
      def changeset(user, params \\ %{}) do
        user
        |> cast(params, [:username, :password, :email, :age])
        |> validate_required([:username, :password])
      end
    end
    
    # 1 : 1  computer - user
    defmodule EctoSample.Schema.Computer do
      use Ecto.Schema
      import Ecto.Changeset
    
      schema "computers" do
        field :hostname, :string
        field :ip,       :string
    
        belongs_to :user, EctoSample.Schema.User
      end
    
      def changeset(computer, params \\ %{}) do
        computer
        |> cast(params, [:hostname, :ip])
        |> validate_required([:hostname, :ip])
      end
    end
    
    # 1 : N  company - user
    defmodule EctoSample.Schema.Company do
      use Ecto.Schema
      import Ecto.Changeset
    
      schema "companys" do
        field :comp_name, :string
        field :addr,      :string
    
        has_many :users, EctoSample.Schema.User
      end
    
      def changeset(company, params \\ %{}) do
        company
        |> cast(params, [:comp_name, :addr])
        |> validate_required([:comp_name, :addr])
      end
    end
    
    # M : N  friend - user
    defmodule EctoSample.Schema.Friend do
      use Ecto.Schema
      import Ecto.Changeset
    
      schema "friends" do
        field :frient_name, :string
        field :phone,       :string
    
        many_to_many :users, EctoSample.Schema.User, join_through: "users_friends"
      end
    
      def changeset(friend, params \\ %{}) do
        friend
        |> cast(params, [:friend_name, :phone])
        |> validate_required([:friend_name, :phone])
      end
    end

    原先的 user 表做了一些修改,增加了一些关联属性,另外增加了 3 张表,和 user 表的关系分别是:

    • 1:1 user : computer
    • 1:N company : user
    • M:N friend : user
  2. table migration
    创建各个表的 migration

    mix ecto.gen.migration create_company
    mix ecto.gen.migration create_computer
    mix ecto.gen.migration create_friend
    mix ecto.gen.migration create_users_friends

    migration 的代码参见:https://gitee.com/wangyubin/ecto_sample.git 整个示例工程的代码都在其中

1:1 示例

def one_to_one() do
  import Ecto.Changeset
  alias EctoSample.Schema.User
  alias EctoSample.Schema.Computer

  # insert
  computer = %Computer{}
  |> Computer.changeset(%{:hostname => "debian", :ip => "192.168.0.100"})
  |> EctoSample.Repo.insert!

  user = %User{}
  |> User.changeset(%{:username => "wyb", :password => "123"})
  |> put_assoc(:computer, computer)
  |> EctoSample.Repo.insert!

  # query
  u = EctoSample.Repo.get!(User, user.id) |> EctoSample.Repo.preload(:computer)
  Logger.info "==============================="
  Logger.info "id:       " <> Integer.to_string(u.id)
  Logger.info "username: " <> u.username
  Logger.info "password: " <> u.password
  Logger.info "computer: *********"
  Logger.info "hostname: " <> u.computer.hostname
  Logger.info "ip:       " <> u.computer.ip
  Logger.info "==============================="
end

1:N 示例

def one_to_many() do
  alias EctoSample.Schema.User
  alias EctoSample.Schema.Company

  # insert
  user1 = EctoSample.Repo.insert!(%User{:username => "wyb001", :password => "123"})
  user2 = EctoSample.Repo.insert!(%User{:username => "wyb002", :password => "321"})
  company = EctoSample.Repo.insert!(%Company{:comp_name => "yunbim", :addr => "D216", :users => [user1, user2]})

  # TODO 这里是根据 user 来新建 company,也可以 根据已有的 company 来创建 user

  # query
  c = EctoSample.Repo.get!(Company, company.id) |> EctoSample.Repo.preload(:users)
  Logger.info "==============================="
  Logger.info "id:       " <> Integer.to_string(c.id)
  Logger.info "comp_name:" <> c.comp_name
  Logger.info "addr    : " <> c.addr
  Logger.info "users: *********"
  c.users |> Enum.map(fn (u) ->
    Logger.info "id:       " <> Integer.to_string(u.id)
    Logger.info "username: " <> u.username
    Logger.info "password: " <> u.password
  end)
  Logger.info "==============================="
end

M:N 示例

def many_to_many() do
  alias EctoSample.Schema.User
  alias EctoSample.Schema.Friend
  import Ecto.Changeset

  # insert
  user1 = EctoSample.Repo.insert!(%User{:username => "wyb001", :password => "123"})
  user2 = EctoSample.Repo.insert!(%User{:username => "wyb002", :password => "321"})
  friend1 = EctoSample.Repo.insert!(%Friend{:friend_name => "f001", :phone => "123456789"})
  friend2 = EctoSample.Repo.insert!(%Friend{:friend_name => "f002", :phone => "987654321"})

  EctoSample.Repo.get!(User, user1.id)
  |> EctoSample.Repo.preload(:friends)
  |> change
  |> put_assoc(:friends, [friend1, friend2])
  |> EctoSample.Repo.update!()

  EctoSample.Repo.get!(User, user2.id)
  |> EctoSample.Repo.preload(:friends)
  |> change
  |> put_assoc(:friends, [friend1])
  |> EctoSample.Repo.update!()

  # query
  f1 = EctoSample.Repo.get!(Friend, friend1.id) |> EctoSample.Repo.preload(:users)
  Logger.info "==============================="
  Logger.info "id:         " <> Integer.to_string(f1.id)
  Logger.info "friend_name:" <> f1.friend_name
  Logger.info "phone:      " <> f1.phone
  Logger.info "users: *********"
  f1.users |> Enum.map(fn (u) ->
    Logger.info "id:       " <> Integer.to_string(u.id)
    Logger.info "username: " <> u.username
    Logger.info "password: " <> u.password
  end)
  Logger.info "==============================="

  f2 = EctoSample.Repo.get!(Friend, friend2.id) |> EctoSample.Repo.preload(:users)
  Logger.info "==============================="
  Logger.info "id:         " <> Integer.to_string(f2.id)
  Logger.info "friend_name:" <> f2.friend_name
  Logger.info "phone:      " <> f2.phone
  Logger.info "users: *********"
  f2.users |> Enum.map(fn (u) ->
    Logger.info "id:       " <> Integer.to_string(u.id)
    Logger.info "username: " <> u.username
    Logger.info "password: " <> u.password
  end)
  Logger.info "==============================="
end

运行测试

$ mix ecto.drop
$ mix ecto.create
$ mix ecto.migrate

$ iex -S mix
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> EctoSample.start
{:ok, #PID<0.182.0>}

iex(2)> EctoSample.one_to_one
11:00:27.800 [info]  ===============================

11:00:27.800 [info]  id:       1

11:00:27.800 [info]  username: wyb

11:00:27.800 [info]  password: 123

11:00:27.800 [info]  computer: *********

11:00:27.800 [info]  hostname: debian

11:00:27.800 [info]  ip:       192.168.0.100

11:00:27.800 [info]  ===============================

iex(3)> EctoSample.one_to_many
11:01:32.327 [info]  ===============================

11:01:32.327 [info]  id:       1

11:01:32.327 [info]  comp_name:yunbim

11:01:32.327 [info]  addr    : D216

11:01:32.327 [info]  users: *********

11:01:32.327 [info]  id:       2

11:01:32.327 [info]  username: wyb001

11:01:32.327 [info]  password: 123

11:01:32.327 [info]  id:       3

11:01:32.327 [info]  username: wyb002

11:01:32.327 [info]  password: 321

11:01:32.327 [info]  ===============================

iex(4)> EctoSample.many_to_many
11:02:22.086 [info]  ===============================

11:02:22.086 [info]  id:         1   

11:02:22.086 [info]  friend_name:f001     

11:02:22.086 [info]  phone:      123456789

11:02:22.086 [info]  users: *********                                                     

11:02:22.086 [info]  id:       4                                 

11:02:22.086 [info]  username: wyb001                                                            

11:02:22.086 [info]  password: 123                  

11:02:22.086 [info]  id:       5

11:02:22.086 [info]  username: wyb002

11:02:22.086 [info]  password: 321

11:02:22.086 [info]  ===============================

11:02:22.087 [info]  ===============================

11:02:22.087 [info]  id:         2

11:02:22.087 [info]  friend_name:f002

11:02:22.087 [info]  phone:      987654321

11:02:22.087 [info]  users: *********

11:02:22.087 [info]  id:       4

11:02:22.087 [info]  username: wyb001

11:02:22.087 [info]  password: 123

11:02:22.087 [info]  ===============================

ecto 中的事务

ecto 中的事务,首先通过 Multi 来组装需要进行的数据库操作,然后通过 Repo.transaction 来执行

def trans() do
  alias EctoSample.Schema.User
  alias EctoSample.Schema.Computer
  import Ecto.Query, only: [from: 2]
  alias Ecto.Multi

  # insert user and computer in one transaction, insert all success
  Logger.info "=========== before transaction==============="
  EctoSample.Repo.one(from u in User, select: count(u.id)) |> Logger.info
  EctoSample.Repo.one(from c in Computer, select: count(c.id)) |> Logger.info

  Multi.new()
  |> Multi.insert(:user, %User{username: "m-user", password: "m-password"})
  |> Multi.insert(:computer, %Computer{hostname: "host-name", ip: "0.0.0.0"})
  |> EctoSample.Repo.transaction
  |> case do
       {:ok, _} -> Logger.info "multi success"
       {:error, _} -> Logger.error "multi error"
     end


  Logger.info "=========== after  transaction==============="
  EctoSample.Repo.one(from u in User, select: count(u.id)) |> Logger.info
  EctoSample.Repo.one(from c in Computer, select: count(c.id)) |> Logger.info

end

插入成功之后,User 和 Computer 表的数据都会增加

其他

除了上述内容之外,Ecto 还有其他的 API 辅助查询和各种数据操作,具体参见 Ecto 文档



本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/p/7897066.html,如需转载请自行联系原作者


目录
相关文章
|
Linux 开发工具 数据安全/隐私保护
​​Linux搭建GitLab私有仓库
GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务。 Gitlab是被广泛使用的基于git的开源代码管理平台, 基于Ruby on Rails构建, 主要针对软件开发过程中产生的代码和文档进行管理, Gitlab主要针对group和project两个维度进行代码和文档管理, 其中group是群组, project是工程项目, 一个group可以管理多个project, 可以理解为一个群组中有多项软件开发任务, 而一个project中可能包含多个branch, 意为每个项目中有多个分支, 分支间相互独立, 不同分支可以进行归并。
|
6月前
|
SQL 资源调度 监控
Flink SQL性能优化实践
Apache Flink流处理性能优化指南:探索数据源读取并行度、状态管理、窗口操作的优化策略,包括设置默认并行度、使用RocksDB状态后端、调整窗口大小。调优方法涉及数据源分区、JOIN条件优化、使用Broadcast JOIN。注意SQL复杂度、并发控制与资源调度,如启用动态资源分配。源码层面优化自定义Source和Sink,利用执行计划分析性能瓶颈。异常检测与恢复通过启用检查点,监控任务性能。预处理数据、使用DISTINCT去重,结合UDF提高效率。选择高效序列化框架和启用数据压缩,优化网络传输和系统配置。处理数据倾斜,均衡数据分布,动态调整资源和任务优先级,以提升整体性能。
233 2
|
6月前
|
iOS开发
Xcode 10 Error:Multiple commands produce问题及解决方案
Xcode 10 Error:Multiple commands produce问题及解决方案
91 0
|
6月前
|
弹性计算 开发者
阿里云99元和199元服务器配置和性能怎么样?
阿里云99元和199元的服务器配置和性能测评如下,阿里云服务器99元一年配置为云服务器ECS经济型e实例,2核2G配置、3M固定带宽和40G ESSD Entry系统盘,新用户和老用户均可买;企业用户ECS u1实例,2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年,企业客户专享。活动如下:
|
6月前
|
安全 Linux 网络安全
服务器设置 SSH 通过密钥登录
服务器设置 SSH 通过密钥登录
|
缓存 安全 Java
Spring面试攻略:如何展现你对Spring的深入理解
本次面试涉及了Spring框架的多个方面,包括IOC和AOP的理解、Spring容器的启动流程、Bean的创建过程、Bean的线程安全性、循环依赖的处理、事务的处理以及Spring MVC中控制器的线程安全性。通过这些问题的回答,展示了对Spring框架的深入理解和应用经验。同时,也凸显了对面试题目的认真思考和清晰表达的能力。
200 0
|
存储 监控 Kubernetes
Docker学习路线10:容器安全
容器安全是实施和管理像Docker这样的容器技术的关键方面。它包括一组实践、工具和技术,旨在保护容器化应用程序及其运行的基础架构。在本节中,我们将讨论一些关键的容器安全考虑因素、最佳实践和建议。
761 1
|
小程序 Java API
阿里云短信服务详细说明与实战开发后端代码(一)
阿里云短信服务详细说明与实战开发后端代码
836 0
|
存储 弹性计算 运维
什么是裸金属服务器?裸金属服务器优势功能说明
什么是裸金属服务器?裸金属服务器优势功能说明
259 1
|
监控 前端开发 JavaScript
【前端监控】前端异常捕获与处理
【前端监控】前端异常捕获与处理
285 0