En robust Java-applikasjon som:
- Kommuniserer med Matrikkel SOAP API (alle tjenester)
- Håndterer server-side filtrering for effektiv datanedlasting
- Lagrer data i PostgreSQL med full relasjonell integritet
- Støtter to-stegs import-pattern for optimal ytelse
- Håndterer eierforhold (personer, juridiske personer, matrikkelenheter)
- Automatisk SOAP serialisering: JAX-WS håndterer komplekse objekter (MatrikkelBubbleId) automatisk
- Type-sikkerhet: Kompileringstidssjekk av alle API-kall
- WSDL-støtte: wsimport genererer perfekte klient-klasser fra WSDL
- Spring Boot: Moderne stack med god PostgreSQL-integrasjon
- Flyway: Database-migrasjoner med versjonskontroll
<properties>
<java.version>21</java.version>
<spring-boot.version>3.4.0</spring-boot.version>
<flyway.version>10.20.1</flyway.version>
<jaxws.version>4.0.0</jaxws.version>
</properties>- Spring Boot 3.4.0 - Latest stable release
- Java 21 - Moderne LTS-versjon med bedre ytelse
- PostgreSQL 17 - Database med full support for komplekse relasjoner
- Flyway 10.20.1 - Database migrations (PostgreSQL 17 kompatibel)
- JAX-WS 4.0.0 - SOAP client implementation
- Spring Data JPA - Database access layer
- Lombok - Reduser boilerplate code
# Java 21 JDK
java --version # skal vise 21.x.x
# Maven 3.9+
mvn --version # skal vise 3.9.x eller nyere
# PostgreSQL 17
psql --version # skal vise 17.xgit clone <repository-url>
cd matrikkel_javaOpprett .env-fil i prosjektets rot:
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=matrikkel2
DB_USERNAME=your_username
DB_PASSWORD=your_password
# Matrikkel API credentials (test environment)
MATRIKKEL_USERNAME=your_kommune_test
MATRIKKEL_PASSWORD=your_password
# Spring Configuration
SPRING_PROFILES_ACTIVE=dev# Generer SOAP-klienter fra WSDL og kompiler
mvn clean compile
# Verifiser at genererte klasser finnes
ls -la target/generated-sources/wsimport/no/matrikkel/client/generated/nedlastning/# Koble til PostgreSQL
psql -U postgres
# Opprett database
CREATE DATABASE matrikkel2;
# Opprett bruker (hvis nødvendig)
CREATE USER your_username WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE matrikkel2 TO your_username;
\q# Last inn environment variables
export $(grep -v '^#' .env | xargs)
# Kjør migrasjoner
mvn flyway:migrate
# Verifiser migrasjons-status
mvn flyway:infoForventet output:
+-----------+---------+------------------+------+---------------------+---------+
| Category | Version | Description | Type | Installed On | State |
+-----------+---------+------------------+------+---------------------+---------+
| Versioned | 1 | baseline schema | SQL | 2025-10-21 12:00:00 | Success |
+-----------+---------+------------------+------+---------------------+---------+
# Koble til database
export $(grep -v '^#' .env | xargs)
psql -h $DB_HOST -p $DB_PORT -U $DB_USERNAME -d $DB_NAME
# Vis alle tabeller
\dt
# Forventet resultat (14 tabeller):
# - matrikkel_matrikkelenheter
# - matrikkel_personer
# - matrikkel_fysiske_personer
# - matrikkel_juridiske_personer
# - matrikkel_eierforhold
# - matrikkel_bygninger
# - matrikkel_bruksenheter
# - matrikkel_veger
# - matrikkel_adresser
# - matrikkel_vegadresser
# - matrikkel_matrikkelenhet_owners (deprecated)
# - matrikkel_person_owners (deprecated)
# - matrikkel_juridisk_person_owners (deprecated)
# - flyway_schema_historyImport kun data for spesifikke personer/organisasjoner:
# Last ned matrikkelenheter + personer + bygninger + adresser for personnummer
export $(grep -v '^#' .env | xargs) && mvn spring-boot:run \
-Dspring-boot.run.arguments="--import --kommune=1103 --personnummer=964965226"
# Eller for organisasjon
export $(grep -v '^#' .env | xargs) && mvn spring-boot:run \
-Dspring-boot.run.arguments="--import --kommune=4601 --organisasjonsnummer=964338531"Hva skjer:
- MatrikkelenhetService filtrerer på server-side (f.eks. 4,744 IDs)
- StoreService henter komplette objekter i batches (500 per batch)
- Person-data lastes (eierforhold)
- Bygninger/bruksenheter lastes (API-filtrert)
- Adresser lastes (API-filtrert)
Ytelse: ~13 sekunder for 4,744 matrikkelenheter (vs minutter for bulk)
Fase 1: Base import (kun matrikkelenheter + personer)
export $(grep -v '^#' .env | xargs) && mvn spring-boot:run \
-Dspring-boot.run.arguments="--import --kommune=1103 --personnummer=964965226 --base-import"Fase 2: Detalj-import (bygninger + adresser fra database-filtrering)
export $(grep -v '^#' .env | xargs) && mvn spring-boot:run \
-Dspring-boot.run.arguments="--filter-existing --kommune=1103 --personnummer=964965226"# Last ned ALLE matrikkelenheter for kommune (ikke anbefalt for store kommuner)
export $(grep -v '^#' .env | xargs) && mvn spring-boot:run \
-Dspring-boot.run.arguments="--import --kommune=4601 --limit=1000"no.matrikkel/
├── config/ # Spring configuration
│ ├── DatabaseConfig.java
│ ├── SoapClientConfig.java
│ └── MatrikkelProperties.java
├── client/ # SOAP client wrappers
│ ├── generated/ # Auto-generated from WSDL
│ │ └── nedlastning/ # UNIFIED package (kritisk!)
│ ├── NedlastningClientWrapper.java
│ ├── StoreClientWrapper.java
│ └── ...
├── domain/
│ ├── entity/ # JPA entities
│ │ ├── Matrikkelenhet.java
│ │ ├── Person.java
│ │ ├── FysiskPerson.java
│ │ ├── JuridiskPerson.java
│ │ ├── Eierforhold.java
│ │ └── ...
│ └── dto/ # Data transfer objects
├── repository/ # Spring Data JPA
│ ├── MatrikkelenhetRepository.java
│ └── ...
├── service/ # Business logic
│ ├── MatrikkelenhetImportService.java
│ ├── PersonImportService.java
│ └── ...
├── mapper/ # Entity ↔ DTO converters
│ ├── MatrikkelenhetMapper.java
│ └── ...
└── cli/ # Command-line interface
└── ImportCommand.java
Kritisk optimalisering for filtrert nedlasting:
// Steg 1: Server-side filtrering (kun IDer)
MatrikkelenhetsokModel model = new MatrikkelenhetsokModel();
model.setKommunenummer(kommunenummer);
model.setNummerForPerson(personnummer);
MatrikkelenhetIdList idList = matrikkelenhetService.findMatrikkelenheter(model, context);
// Steg 2: Batch-hent komplette objekter
MatrikkelBubbleIdList bubbleIdList = convertToMatrikkelBubbleIdList(idList);
MatrikkelBubbleObjectList objects = storeService.getObjects(bubbleIdList, context);Fordeler:
- ✅ 99% reduksjon i dataoverføring
- ✅ Server gjør filtreringen (raskere)
- ✅ Batch-processing (500 objekter per batch)
<!-- pom.xml - ALLE tjenester bruker nedlastning package -->
<execution>
<id>wsimport-store</id>
<configuration>
<packageName>no.matrikkel.client.generated.nedlastning</packageName>
</configuration>
</execution>
<execution>
<id>wsimport-nedlastning</id>
<configuration>
<packageName>no.matrikkel.client.generated.nedlastning</packageName>
</configuration>
</execution>Hvorfor?
- StoreService og NedlastningService deler samme XSD schema
- Forskjellige packages →
ClassCastExceptionruntime! - Unified package → type-safe casting ✅
Se PACKAGE_CONSOLIDATION_FIX.md for detaljer.
Hovedtabeller:
-
matrikkel_matrikkelenheter - Matrikkelenheter (grunndata)
- Primærnøkkel:
matrikkel_matrikkelenhet_id - Business key: (kommunenummer, gardsnummer, bruksnummer, festenummer, seksjonsnummer)
- Primærnøkkel:
-
matrikkel_personer - Personer (base-tabell for inheritance)
- Primærnøkkel:
id(JPA auto-generated) - API ID:
matrikkel_person_id - Viktig:
nummerinneholder både personnummer OG organisasjonsnummer!
- Primærnøkkel:
-
matrikkel_fysiske_personer - Fysiske personer (extends Person)
- Join:
fysisk_person_entity_id→matrikkel_personer.id
- Join:
-
matrikkel_juridiske_personer - Juridiske personer (extends Person)
- Join:
juridisk_person_entity_id→matrikkel_personer.id
- Join:
-
matrikkel_eierforhold - Eierforhold (linking table)
- Join til matrikkelenhet:
matrikkelenhet_id - Join til person:
fysisk_person_idellerjuridisk_person_entity_id - Deprecated fields:
person_owner_id,juridisk_person_owner_id,matrikkelenhet_owner_id
- Join til matrikkelenhet:
-
matrikkel_bygninger - Bygninger
-
matrikkel_bruksenheter - Bruksenheter
-
matrikkel_veger - Veger (må lastes FØR adresser!)
-
matrikkel_adresser - Adresser (base-tabell)
-
matrikkel_vegadresser - Vegadresser (extends Adresse)
Se docs/DATABASE_SCHEMA.md for komplett skjema.
# Kompiler prosjekt og generer SOAP-klienter
mvn clean compile
# Kjør tester
mvn test
# Package til JAR
mvn clean package -DskipTests
# Kjør applikasjon
export $(grep -v '^#' .env | xargs) && mvn spring-boot:run \
-Dspring-boot.run.arguments="--import --kommune=1103 --personnummer=964965226"# Koble til database
export $(grep -v '^#' .env | xargs)
psql -h $DB_HOST -p $DB_PORT -U $DB_USERNAME -d $DB_NAME
# Tell matrikkelenheter
SELECT COUNT(*) FROM matrikkel_matrikkelenheter;
# Se eierforhold for person
SELECT m.matrikkelnummer_tekst, p.navn, e.andel_teller, e.andel_nevner
FROM matrikkel_eierforhold e
JOIN matrikkel_matrikkelenheter m ON e.matrikkelenhet_id = m.matrikkel_matrikkelenhet_id
JOIN matrikkel_personer p ON e.fysisk_person_id = p.id OR e.juridisk_person_entity_id = p.id
WHERE p.nummer = '964965226'
LIMIT 10;
# Vis alle tabeller med antall rader
SELECT
schemaname,
tablename,
(xpath('/row/cnt/text()', xml_count))[1]::text::int as row_count
FROM (
SELECT
schemaname,
tablename,
query_to_xml(format('select count(*) as cnt from %I.%I', schemaname, tablename), false, true, '') as xml_count
FROM pg_tables
WHERE schemaname = 'public'
AND tablename LIKE 'matrikkel_%'
) t
ORDER BY row_count DESC;Applikasjonen logger til konsoll med følgende nivåer:
# application-dev.yml
logging:
level:
no.matrikkel: DEBUG # Vår egen kode
org.springframework.web: INFO
org.hibernate.SQL: DEBUG # SQL-spørringer
com.sun.xml.ws: WARN # SOAP client (kun warnings)1. Transaksjonshåndtering - Per-Batch Commits
// ❌ FEIL - Outer transaction "svelger" inner commits
@Transactional
public void importAll() {
for (batch : batches) {
personService.saveBatch(batch); // Commit skjer ikke før metoden er ferdig!
}
}
// ✅ RIKTIG - Ingen outer transaction, inner commits umiddelbart
public void importAll() {
for (batch : batches) {
personService.saveBatch(batch); // @Transactional - commits etter hver batch!
}
}2. N+1 Query Prevention
// ❌ FEIL - N+1 queries
for (Matrikkelenhet m : matrikkelenheter) {
Veg veg = vegRepository.findById(vegId); // Database query per iteration!
}
// ✅ RIKTIG - Load into Map first (O(1) lookup)
Map<Long, Veg> vegerMap = veger.stream()
.collect(Collectors.toMap(Veg::getVegId, Function.identity()));
for (Matrikkelenhet m : matrikkelenheter) {
Veg veg = vegerMap.get(vegId); // In-memory lookup!
}3. Person Number Filtering
// ❌ FEIL - Disse feltene er NULL!
fysiskPersonRepository.findByFodselsnummer(nummer);
juridiskPersonRepository.findByOrganisasjonsnummer(nummer);
// ✅ RIKTIG - Bruk Person.nummer (universelt felt)
personRepository.findByNummer(nummer);# Kjør alle tester
mvn test
# Kjør spesifikk test
mvn test -Dtest=MatrikkelenhetImportServiceTest
# Kjør med coverage
mvn test jacoco:reportProblem: ClassCastException ved runtime
nedlastning.Grunneiendom cannot be cast to store.Matrikkelenhet
Løsning: Alle WSDL-tjenester må bruke samme package! Se PACKAGE_CONSOLIDATION_FIX.md
Problem: Flyway migrerer til feil database
# Verifiser at pom.xml bruker environment variables
grep -A 5 "flyway-maven-plugin" pom.xml
# Skal vise: ${env.DB_HOST}, ${env.DB_PORT}, ${env.DB_NAME}Løsning: Oppdater pom.xml Flyway-konfigurasjon til å bruke environment variables.
Problem: Adresser blir skippet (0 saved)
Mapped 0 adresser: 0 vegadresser, 0 matrikkeladresser (37 skipped)
Løsning: Last ned veger FØR adresser! AdresseMapper krever Veg-entiteter i database.
# RIKTIG rekkefølge:
fetchAndSaveVegData(kommunenummer); # 1. Last veger
fetchAndSaveAdresseData(matrikkelenheter); # 2. Last adresser- README.md - Quick start guide
- docs/DATABASE_SCHEMA.md - Database-skjema
- PACKAGE_CONSOLIDATION_FIX.md - Kritisk WSDL package-fix
- STORESERVICE_SOLUTION.md - Optimal filtrert nedlasting
- .github/copilot-instructions.md - Kode-retningslinjer
Dette prosjektet er utviklet for integrasjon med Kartverkets Matrikkel API.