Inhaltsverzeichnis
golang operation mysql
Installation
Datenbank verbinden
# 🎜🎜# Die Abfragesyntax mit Query wird später erwähnt Die Datenbank wurde deklariert und ihre Deklaration befindet sich in der Datenbank. Wir können sie im Voraus deklarieren, damit sie an anderer Stelle wiederverwendet werden kann.
Query
Der von Query zurückgegebene Fehler kann auftreten, wenn der Server die Abfrage vorbereitet, oder er kann auftreten, wenn die Abfrageanweisung ausgeführt wird. Selbst wenn die Datenbank beispielsweise zehnmal versucht, eine verfügbare Verbindung zu finden oder zu erstellen, ist es möglich, dass eine fehlerhafte Verbindung aus dem Verbindungspool abgerufen wird. Normalerweise entstehen Fehler hauptsächlich durch Fehler in SQL-Anweisungen, ähnliche Matching-Fehler und Fehler in Domänennamen oder Tabellennamen.
QueryRow und QueryRowx
Get and Select ist eine sehr zeitsparende Erweiterung, die das Ergebnis direkt der Struktur zuordnen kann und StructScan intern kapselt . Transformation. Verwenden Sie die Get-Methode, um ein einzelnes Ergebnis zu erhalten, und verwenden Sie dann die Scan-Methode sowie die Select-Methode, um Ergebnisausschnitte zu erhalten.
Transaktionen
Commit(): Transaktion senden (SQL ausführen)
连接池设置
案例使用
Heim Datenbank MySQL-Tutorial So verbinden Sie Golang mit der MySQL-Datenbank

So verbinden Sie Golang mit der MySQL-Datenbank

May 26, 2023 am 11:05 AM
mysql golang

golang operation mysql

Installation

go get "github.com/go-sql-driver/mysql"
go get "github.com/jmoiron/sqlx"
Nach dem Login kopieren

Datenbank verbinden

var Db *sqlx.DB
db, err := sqlx.Open("mysql","username:password@tcp(ip:port)/database?charset=utf8")
Db = db
Nach dem Login kopieren

Verbindung 2#🎜 🎜#

package main
import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB  //全局对象db

func initDB() (err error) {
  db, err = sql.Open("mysql","root:admin123@tcp(127.0.0.1:3306)/dududu?charset=utf8")
  if err!=nil{
    return err
  }
  err = db.Ping()  //校验数据库连接
  if err!=nil{
    return err
  }
  return nil
}

type beautiful struct {
	spu_id string
	title string
	price string
}

func queryRowDemo()  {
	sqlStr :="select spu_id,title,price from dududu_shops where id = ?"
	var u beautiful
	err:=db.QueryRow(sqlStr,101).Scan(&u.spu_id,&u.title,&u.price)
	if err!=nil{
		fmt.Println("2",err)
	}
	fmt.Println(u)
}


func main()  {
	err:=initDB()
	if err!=nil{
		fmt.Println("1",err)
		return
	}
	queryRowDemo()
}
Nach dem Login kopieren

Handle-Typen

SQLX-Design und Datenbank-/SQL-Nutzung sind gleich. Enthält 4 Haupthandle-Typen:

  • sqlx.DB – stellt ähnlich wie sql.DB die Datenbank dar.

  • sqlx.Tx – Ähnlich wie sql.Tx stellt es Dinge dar.

  • sqlx.Stmt – Ähnlich wie sql.Stmt stellt es eine vorbereitete Anweisung dar.

  • sqlx.NamedStmt – stellt eine vorbereitete Anweisung dar (unterstützt benannte Parameter)

Alle Handlertypen sind kompatibel mit „database/sql“ bedeutet, dass Sie sqlx.DB.Query direkt durch „sql.DB.Query“ ersetzen können. Dadurch lässt sich sqlx einfach zu vorhandenen Datenbankprojekten hinzufügen.

Darüber hinaus verfügt sqlx auch über zwei Cursortypen:

  • sqlx.Rows – Ähnlich wie sql.Rows gibt Queryx zurück.

  • sqlx.Row – Ähnlich wie sql.Row gibt QueryRowx zurück.

Im Vergleich zur Datenbank/SQL-Methode gibt es eine neue Syntax, was bedeutet, dass die erhaltenen Daten direkt in eine Struktur umgewandelt werden können.

  • Get(dest interface{}, …) error

  • Select(dest interface{}, …) Fehler

Tabelle erstellen

Alle folgenden Beispiele haben die folgende Tabellenstruktur als Grundlage für die Operation.

CREATE TABLE `userinfo` (
    `uid` INT(10) NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(64)  DEFAULT NULL,
    `password` VARCHAR(32)  DEFAULT NULL,
    `department` VARCHAR(64)  DEFAULT NULL,
    `email` varchar(64) DEFAULT NULL,
    PRIMARY KEY (`uid`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8
Nach dem Login kopieren

Exec verwendet

Exec und MustExec nehmen eine Verbindung aus dem Verbindungspool heraus und führen dann den entsprechenden Abfragevorgang aus. Für Treiber, die die Ad-hoc-Abfrageausführung nicht unterstützen, wird hinter der Operationsausführung eine vorbereitete Anweisung erstellt. Diese Verbindung wird an den Verbindungspool zurückgegeben, bevor das Ergebnis zurückgegeben wird.

Es ist zu beachten, dass verschiedene Datenbanktypen unterschiedliche Platzhalter verwenden. als Platzhaltersymbol.

  • MySQL verwendet?

  • PostgreSQL verwendet 1,1,2 usw.

  • SQLite verwendet? Oder $1

  • Oracle-Verwendung: Name

Exec, um dieses Beispiel hinzuzufügen oder zu löschen

# 🎜🎜# Die Abfragesyntax mit Query wird später erwähnt Die Datenbank wurde deklariert und ihre Deklaration befindet sich in der Datenbank. Wir können sie im Voraus deklarieren, damit sie an anderer Stelle wiederverwendet werden kann.

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    "fmt"
)

var Db *sqlx.DB

func init()  {
    db, err := sqlx.Open("mysql", "stu:1234qwer@tcp(10.0.0.241:3307)/test?charset=utf8")
    if err != nil {
        fmt.Println("open mysql failed,", err)
        return
    }
    Db = db
}

func main()  {
    result, err := Db.Exec("INSERT INTO userinfo (username, password, department,email) VALUES (?, ?, ?,?)","wd","123","it","wd@163.com")
    if err != nil{
        fmt.Println("insert failed,error: ", err)
        return
    }
    id,_ := result.LastInsertId()
    fmt.Println("insert id is :",id)
    _, err1 := Db.Exec("update userinfo set username = ? where uid = ?","jack",1)
    if err1 != nil{
        fmt.Println("update failed error:",err1)
    } else {
        fmt.Println("update success!")
    }
    _, err2 := Db.Exec("delete from userinfo where uid = ? ", 1)
    if err2 != nil{
        fmt.Println("delete error:",err2)
    }else{
        fmt.Println("delete success")
    }

}
//insert id is : 1
//update success!
//delete success
Nach dem Login kopieren

Natürlich stellt sqlx auch Preparex() für die Erweiterung bereit, das direkt für die Strukturkonvertierung verwendet werden kann

stmt, err := db.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = stmt.QueryRow(65)
 
tx, err := db.Begin()
txStmt, err := tx.Prepare(`SELECT * FROM place WHERE telcode=?`)
row = txStmt.QueryRow(852)
Nach dem Login kopieren

Query

row results by using Zu erhaltende Datenbankzeilen in /sql. Die Abfrage gibt ein sql.Rows-Objekt und ein Fehlerobjekt zurück.

Wenn Sie es verwenden, sollten Sie Zeilen als Cursor und nicht als Ergebnisreihe behandeln. Obwohl die Methoden des datenbankgesteuerten Cachings unterschiedlich sind, wird jedes Mal eine Ergebnisspalte durch Next()-Iteration abgerufen. Wenn die Abfrageergebnisse sehr groß sind, kann die Speichernutzung mithilfe von Scan() effektiv begrenzt werden von SQL-Ergebnissen zum Mitnehmen. Wenn Sie nicht alle Zeilenergebnisse durchlaufen, rufen Sie unbedingt rows.Close() auf, bevor Sie die Verbindung an den Verbindungspool zurückgeben.

Der von Query zurückgegebene Fehler kann auftreten, wenn der Server die Abfrage vorbereitet, oder er kann auftreten, wenn die Abfrageanweisung ausgeführt wird. Selbst wenn die Datenbank beispielsweise zehnmal versucht, eine verfügbare Verbindung zu finden oder zu erstellen, ist es möglich, dass eine fehlerhafte Verbindung aus dem Verbindungspool abgerufen wird. Normalerweise entstehen Fehler hauptsächlich durch Fehler in SQL-Anweisungen, ähnliche Matching-Fehler und Fehler in Domänennamen oder Tabellennamen.

In den meisten Fällen erstellt Rows.Scan() eine Kopie der vom Treiber erhaltenen Daten, unabhängig davon, wie der Treiber den Cache verwendet. Der spezielle Typ sql.RawBytes kann verwendet werden, um aus den vom Treiber zurückgegebenen Daten ein Zero-Copy-Slice-Byte abzurufen. Beim nächsten Aufruf von Next ist der Wert ungültig, da der Speicher, auf den er verweist, vom Treiber mit anderen Daten überschrieben wurde.

Die von Query verwendete Verbindung wird freigegeben, nachdem alle Zeilen durch Next() durchlaufen wurden oder nachdem rows.Close() aufgerufen wurde.

Beispiel:

stmt, err := db.Preparex(`SELECT * FROM place WHERE telcode=?`)
var p Place
err = stmt.Get(&p, 852)
Nach dem Login kopieren

Queryx

Queryx verhält sich sehr ähnlich wie Query, gibt jedoch ein sqlx.Rows-Objekt zurück und unterstützt erweitertes Scanverhalten in Strukturen umgewandelt werden.

Beispiel:

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    "fmt"
)

var Db *sqlx.DB

func init()  {
    db, err := sqlx.Open("mysql", "stu:1234qwer@tcp(10.0.0.241:3307)/test?charset=utf8")
    if err != nil {
        fmt.Println("open mysql failed,", err)
        return
    }
    Db = db
}

func main()  {
    rows, err := Db.Query("SELECT username,password,email FROM userinfo")
    if err != nil{
        fmt.Println("query failed,error: ", err)
        return
    }
    for rows.Next() {  //循环结果
        var username,password,email string
        err = rows.Scan(&username, &password, &email)
        println(username,password,email)
    }
    
}
//wd 123 wd@163.com
//jack 1222 jack@165.com
Nach dem Login kopieren

QueryRow und QueryRowx

Sowohl QueryRow als auch QueryRowx beziehen ein Datenelement aus der Datenbank, aber QueryRowx stellt eine Scan-Erweiterung bereit kann direkt verwendet werden Konvertieren Sie das Ergebnis in eine Struktur.

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    "fmt"
)

var Db *sqlx.DB

type stu struct {
    Username string   `db:"username"`
    Password string      `db:"password"`
    Department string  `db:"department"`
    Email string        `db:"email"`
}

func init()  {
    db, err := sqlx.Open("mysql", "stu:1234qwer@tcp(10.0.0.241:3307)/test?charset=utf8")
    if err != nil {
        fmt.Println("open mysql failed,", err)
        return
    }
    Db = db
}

func main()  {
    rows, err := Db.Queryx("SELECT username,password,email FROM userinfo")
    if err != nil{
        fmt.Println("Qeryx failed,error: ", err)
        return
    }
    for rows.Next() {  //循环结果
        var stu1 stu
        err = rows.StructScan(&stu1)// 转换为结构体
        fmt.Println("stuct data:",stu1.Username,stu1.Password)
    }
}
//stuct data: wd 123
//stuct data: jack 1222
Nach dem Login kopieren

Get and Select (sehr häufig verwendet)

Get and Select ist eine sehr zeitsparende Erweiterung, die das Ergebnis direkt der Struktur zuordnen kann und StructScan intern kapselt . Transformation. Verwenden Sie die Get-Methode, um ein einzelnes Ergebnis zu erhalten, und verwenden Sie dann die Scan-Methode sowie die Select-Methode, um Ergebnisausschnitte zu erhalten.

Beispiel:

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    "fmt"
)

var Db *sqlx.DB

type stu struct {
    Username string   `db:"username"`
    Password string      `db:"password"`
    Department string  `db:"department"`
    Email string        `db:"email"`
}

func init()  {
    db, err := sqlx.Open("mysql", "stu:1234qwer@tcp(10.0.0.241:3307)/test?charset=utf8")
    if err != nil {
        fmt.Println("open mysql failed,", err)
        return
    }
    Db = db
}

func main()  {
    row := Db.QueryRow("SELECT username,password,email FROM userinfo where uid = ?",1) // QueryRow返回错误,错误通过Scan返回
    var username,password,email string
    err :=row.Scan(&username,&password,&email)
    if err != nil{
        fmt.Println(err)
    }
    fmt.Printf("this is QueryRow res:[%s:%s:%s]\n",username,password,email)
    var s stu
    err1 := Db.QueryRowx("SELECT username,password,email FROM userinfo where uid = ?",2).StructScan(&s)
    if err1 != nil{
        fmt.Println("QueryRowx error :",err1)
    }else {
        fmt.Printf("this is QueryRowx res:%v",s)
    }
}
//this is QueryRow res:[wd:123:wd@163.com]
//this is QueryRowx res:{jack 1222  jack@165.com}
Nach dem Login kopieren

Transaktionen

Transaktionsvorgänge werden durch drei Methoden implementiert:

Begin( ): Offene Transaktion

Commit(): Transaktion senden (SQL ausführen)

Rollback(): Rollback

Nutzungsprozess: #🎜🎜 #

tx, err := db.Begin()
err = tx.Exec(...)
err = tx.Commit()

//或者使用sqlx扩展的事务
tx := db.MustBegin()
tx.MustExec(...)
err = tx.Commit()
Nach dem Login kopieren

为了维持一个持续的连接状态,Tx对象必须绑定和控制单个连接。在整个生命周期期间,一个Tx将保持连接,直到调用commit或Rollback()方法将其释放。必须非常谨慎地调用这些函数,否则连接将一直被占用,直到被垃圾回收。

使用示例:

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    "fmt"
)

var Db *sqlx.DB

func init()  {
    db, err := sqlx.Open("mysql", "stu:1234qwer@tcp(10.0.0.241:3307)/test?charset=utf8")
    if err != nil {
        fmt.Println("open mysql failed,", err)
        return
    }
    Db = db
}

func main()  {
    tx, err := Db.Beginx()
    _, err = tx.Exec("insert into userinfo(username,password) values(?,?)", "Rose","2223")
    if err != nil {
        tx.Rollback()
    }
    _, err = tx.Exec("insert into userinfo(username,password) values(?,?)", "Mick",222)
    if err != nil {
        fmt.Println("exec sql error:",err)
        tx.Rollback()
    }
    err = tx.Commit()
    if err != nil {
        fmt.Println("commit error")
    }

}
Nach dem Login kopieren

连接池设置

当连接池中没有可用空闲连接时,连接池会自动创建连接,并且其增长是无限制的。最大连接数可以通过调用DB.SetMaxOpenConns来设置。未使用的连接标记为空闲,如果不需要则关闭。为了减少连接的开启和关闭次数,可以利用DB.SetMaxIdleConns方法来设定最大的空闲连接数。

注意:该设置方法golang版本至少为1.2

  • DB.SetMaxIdleConns(n int) 设置最大空闲连接数

  • DB.SetMaxOpenConns(n int) 设置最大打开的连接数

  • DB.SetConnMaxIdleTime(time.Second*10) 间隔时间

示例:

package main

import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jmoiron/sqlx"
    "fmt"
)

var Db *sqlx.DB

func init()  {
    db, err := sqlx.Open("mysql", "stu:1234qwer@tcp(10.0.0.241:3307)/test?charset=utf8")
    if err != nil {
        fmt.Println("open mysql failed,", err)
        return
    }
    Db = db
    Db.SetMaxOpenConns(30)
    Db.SetMaxIdleConns(15)

}
Nach dem Login kopieren

案例使用

var Db *sqlx.DB
db, err := sqlx.Open("mysql","root:admin123@tcp(127.0.0.1:3306)/dududu?charset=utf8")
Db = db

rows, err := Db.Query("SELECT spu_id,title,price FROM dududu_shops")

if err != nil{
  fmt.Println("query failed,error: ", err)
  return
}
for rows.Next() {  //循环结果
  var spu_id,title,price string
  err = rows.Scan(&spu_id, &title, &price)
  println(spu_id,title,price)
}
Nach dem Login kopieren

Das obige ist der detaillierte Inhalt vonSo verbinden Sie Golang mit der MySQL-Datenbank. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Mysqls Platz: Datenbanken und Programmierung Mysqls Platz: Datenbanken und Programmierung Apr 13, 2025 am 12:18 AM

Die Position von MySQL in Datenbanken und Programmierung ist sehr wichtig. Es handelt sich um ein Open -Source -Verwaltungssystem für relationale Datenbankverwaltung, das in verschiedenen Anwendungsszenarien häufig verwendet wird. 1) MySQL bietet effiziente Datenspeicher-, Organisations- und Abruffunktionen und unterstützt Systeme für Web-, Mobil- und Unternehmensebene. 2) Es verwendet eine Client-Server-Architektur, unterstützt mehrere Speichermotoren und Indexoptimierung. 3) Zu den grundlegenden Verwendungen gehören das Erstellen von Tabellen und das Einfügen von Daten, und erweiterte Verwendungen beinhalten Multi-Table-Verknüpfungen und komplexe Abfragen. 4) Häufig gestellte Fragen wie SQL -Syntaxfehler und Leistungsprobleme können durch den Befehl erklären und langsam abfragen. 5) Die Leistungsoptimierungsmethoden umfassen die rationale Verwendung von Indizes, eine optimierte Abfrage und die Verwendung von Caches. Zu den Best Practices gehört die Verwendung von Transaktionen und vorbereiteten Staten

So stellen Sie eine Verbindung zur Datenbank von Apache her So stellen Sie eine Verbindung zur Datenbank von Apache her Apr 13, 2025 pm 01:03 PM

Apache verbindet eine Verbindung zu einer Datenbank erfordert die folgenden Schritte: Installieren Sie den Datenbanktreiber. Konfigurieren Sie die Datei web.xml, um einen Verbindungspool zu erstellen. Erstellen Sie eine JDBC -Datenquelle und geben Sie die Verbindungseinstellungen an. Verwenden Sie die JDBC -API, um über den Java -Code auf die Datenbank zuzugreifen, einschließlich Verbindungen, Erstellen von Anweisungen, Bindungsparametern, Ausführung von Abfragen oder Aktualisierungen und Verarbeitungsergebnissen.

So starten Sie MySQL von Docker So starten Sie MySQL von Docker Apr 15, 2025 pm 12:09 PM

Der Prozess des Startens von MySQL in Docker besteht aus den folgenden Schritten: Ziehen Sie das MySQL -Image zum Erstellen und Starten des Containers an, setzen

CentOS installieren MySQL CentOS installieren MySQL Apr 14, 2025 pm 08:09 PM

Die Installation von MySQL auf CentOS umfasst die folgenden Schritte: Hinzufügen der entsprechenden MySQL Yum -Quelle. Führen Sie den Befehl mySQL-server aus, um den MySQL-Server zu installieren. Verwenden Sie den Befehl mySQL_SECURE_INSTALLATION, um Sicherheitseinstellungen vorzunehmen, z. B. das Festlegen des Stammbenutzerkennworts. Passen Sie die MySQL -Konfigurationsdatei nach Bedarf an. Tune MySQL -Parameter und optimieren Sie Datenbanken für die Leistung.

MySQLs Rolle: Datenbanken in Webanwendungen MySQLs Rolle: Datenbanken in Webanwendungen Apr 17, 2025 am 12:23 AM

Die Hauptaufgabe von MySQL in Webanwendungen besteht darin, Daten zu speichern und zu verwalten. 1.Mysql verarbeitet effizient Benutzerinformationen, Produktkataloge, Transaktionsunterlagen und andere Daten. 2. Durch die SQL -Abfrage können Entwickler Informationen aus der Datenbank extrahieren, um dynamische Inhalte zu generieren. 3.Mysql arbeitet basierend auf dem Client-Server-Modell, um eine akzeptable Abfragegeschwindigkeit sicherzustellen.

So installieren Sie MySQL in CentOS7 So installieren Sie MySQL in CentOS7 Apr 14, 2025 pm 08:30 PM

Der Schlüssel zur eleganten Installation von MySQL liegt darin, das offizielle MySQL -Repository hinzuzufügen. Die spezifischen Schritte sind wie folgt: Laden Sie den offiziellen GPG -Schlüssel von MySQL herunter, um Phishing -Angriffe zu verhindern. Add MySQL repository file: rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm Update yum repository cache: yum update installation MySQL: yum install mysql-server startup MySQL service: systemctl start mysqld set up booting

C und Golang: Wenn die Leistung von entscheidender Bedeutung ist C und Golang: Wenn die Leistung von entscheidender Bedeutung ist Apr 13, 2025 am 12:11 AM

C eignet sich besser für Szenarien, in denen eine direkte Kontrolle der Hardware -Ressourcen und hohe Leistungsoptimierung erforderlich ist, während Golang besser für Szenarien geeignet ist, in denen eine schnelle Entwicklung und eine hohe Parallelitätsverarbeitung erforderlich sind. 1.Cs Vorteil liegt in den nahezu Hardware-Eigenschaften und hohen Optimierungsfunktionen, die für leistungsstarke Bedürfnisse wie die Spieleentwicklung geeignet sind. 2. Golangs Vorteil liegt in seiner präzisen Syntax und der natürlichen Unterstützung, die für die Entwicklung einer hohen Parallelitätsdienste geeignet ist.

Das Performance -Rennen: Golang gegen C. Das Performance -Rennen: Golang gegen C. Apr 16, 2025 am 12:07 AM

Golang und C haben jeweils ihre eigenen Vorteile bei Leistungswettbewerben: 1) Golang ist für eine hohe Parallelität und schnelle Entwicklung geeignet, und 2) C bietet eine höhere Leistung und eine feinkörnige Kontrolle. Die Auswahl sollte auf Projektanforderungen und Teamtechnologie -Stack basieren.

See all articles